Skip to main content
The Toolbar component provides a fixed or floating bar with buttons for common editor operations like formatting text, inserting content, and managing lists.

Features

  • Pre-built buttons for common formatting operations
  • Automatic state management (active/disabled states)
  • Customizable button layout
  • Tooltip support
  • Framework-agnostic implementation

Installation

import { ProseKit } from 'prosekit/react'
import { useEditor } from 'prosekit/react'

Basic Usage

import { useEditor, useEditorDerivedValue } from 'prosekit/react'

function Toolbar() {
  const editor = useEditor()
  
  const canBold = useEditorDerivedValue(
    () => editor.commands.toggleBold.canExec()
  )
  const isBold = useEditorDerivedValue(
    () => editor.marks.bold.isActive()
  )

  return (
    <div className="toolbar">
      <button
        disabled={!canBold}
        onClick={() => editor.commands.toggleBold()}
        data-active={isBold}
      >
        Bold
      </button>
    </div>
  )
}

Full Example

Here’s a complete toolbar with multiple formatting options:
import type { Editor } from 'prosekit/core'
import { useEditor, useEditorDerivedValue } from 'prosekit/react'

function getToolbarState(editor: Editor) {
  return {
    bold: {
      isActive: editor.marks.bold.isActive(),
      canExec: editor.commands.toggleBold.canExec(),
    },
    italic: {
      isActive: editor.marks.italic.isActive(),
      canExec: editor.commands.toggleItalic.canExec(),
    },
    underline: {
      isActive: editor.marks.underline.isActive(),
      canExec: editor.commands.toggleUnderline.canExec(),
    },
  }
}

export default function Toolbar() {
  const editor = useEditor()
  const state = useEditorDerivedValue(getToolbarState)

  return (
    <div className="toolbar">
      <button
        disabled={!state.bold.canExec}
        data-active={state.bold.isActive}
        onClick={() => editor.commands.toggleBold()}
      >
        Bold
      </button>
      
      <button
        disabled={!state.italic.canExec}
        data-active={state.italic.isActive}
        onClick={() => editor.commands.toggleItalic()}
      >
        Italic
      </button>
      
      <button
        disabled={!state.underline.canExec}
        data-active={state.underline.isActive}
        onClick={() => editor.commands.toggleUnderline()}
      >
        Underline
      </button>
    </div>
  )
}

Common Toolbar Items

Here are common commands you can add to your toolbar:

Text Formatting

  • toggleBold() - Toggle bold formatting
  • toggleItalic() - Toggle italic formatting
  • toggleUnderline() - Toggle underline
  • toggleStrike() - Toggle strikethrough
  • toggleCode() - Toggle inline code

Block Types

  • toggleHeading({ level: 1 }) - Toggle heading (levels 1-6)
  • setParagraph() - Convert to paragraph
  • setCodeBlock() - Insert code block
  • toggleBlockquote() - Toggle blockquote

Lists

  • toggleList({ kind: 'bullet' }) - Toggle bullet list
  • toggleList({ kind: 'ordered' }) - Toggle numbered list
  • toggleList({ kind: 'task' }) - Toggle task list
  • indentList() - Increase indentation
  • dedentList() - Decrease indentation

History

  • undo() - Undo last change
  • redo() - Redo last undone change

Styling

The toolbar component doesn’t include default styles, giving you full control over appearance:
.toolbar {
  display: flex;
  gap: 4px;
  padding: 8px;
  border-bottom: 1px solid #e5e7eb;
}

.toolbar button {
  padding: 6px 12px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
}

.toolbar button:hover {
  background: #f3f4f6;
}

.toolbar button[data-active="true"] {
  background: #e5e7eb;
}

.toolbar button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

Positioning

Fixed Toolbar

<div className="editor-container">
  <Toolbar />
  <div ref={editor.mount} />
</div>

Floating Toolbar

For a floating toolbar, position it absolutely:
.toolbar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: white;
}

API Reference

Editor Commands

All toolbar buttons typically use editor commands. Access them via:
editor.commands.commandName()

Editor State

Check active states and command availability:
// Check if a mark is active
editor.marks.bold.isActive()

// Check if a node is active
editor.nodes.heading.isActive({ level: 1 })

// Check if a command can be executed
editor.commands.toggleBold.canExec()

Hooks

useEditor
() => Editor
Returns the current editor instance from context.
useEditorDerivedValue
<T>(fn: (editor: Editor) => T) => T
Computes a derived value from the editor state that updates on every editor change.

Best Practices

  1. Use derived state: Always use useEditorDerivedValue to compute toolbar state to ensure buttons update when the selection or document changes
  2. Group related commands: Organize buttons into logical groups (formatting, blocks, lists, etc.)
  3. Visual feedback: Show active states for toggle commands and disable buttons when commands can’t execute
  4. Keyboard shortcuts: Consider showing keyboard shortcuts in tooltips
  5. Responsive design: For mobile, consider collapsing less-used commands into a dropdown menu

See Also