Skip to main content
Build your own toolbar UI. SuperDoc provides the state and commands — you render whatever you want.

Quick start

import { createHeadlessToolbar } from 'superdoc/headless-toolbar';

const superdoc = new SuperDoc({
  selector: '#editor',
  document: '/contract.docx',
});

const toolbar = createHeadlessToolbar({
  superdoc,
  commands: ['bold', 'italic', 'underline', 'font-size', 'link', 'undo', 'redo'],
});

toolbar.subscribe(({ snapshot }) => {
  renderToolbar(snapshot);
});

toolbar.execute('bold');
Don’t pass toolbar to the SuperDoc constructor. The headless toolbar replaces the built-in UI entirely — no flags needed.

Core concepts

Snapshot

Every time the editor state changes, the toolbar produces a ToolbarSnapshot. Read it to know what your UI should look like.
snapshot.commands['bold']?.active    // true if bold is on
snapshot.commands['bold']?.disabled  // true if the command can't run
snapshot.commands['font-size']?.value // '12pt' — the current value

Execute

Run a command by ID. The editor gets focus back automatically — no need for onMouseDown={e => e.preventDefault()} or manual focus management.
toolbar.execute('bold');
toolbar.execute('font-size', '14pt');
toolbar.execute('text-color', '#ff0000');
toolbar.execute('zoom', 125);

Subscribe

subscribe() fires immediately with the current snapshot, then again on every change. It returns an unsubscribe function.
const unsub = toolbar.subscribe(({ snapshot }) => {
  updateUI(snapshot);
});

// Later:
unsub();

API reference

createHeadlessToolbar(options)

Creates a headless toolbar controller bound to a SuperDoc instance.
options.superdoc
SuperDoc
required
The SuperDoc instance to bind to.
options.commands
PublicToolbarItemId[]
Command IDs to track. When omitted, all available commands are tracked.
Returns a HeadlessToolbarController.

HeadlessToolbarController

getSnapshot()

Returns the current ToolbarSnapshot without subscribing.
const snapshot = toolbar.getSnapshot();
const isBold = snapshot.commands['bold']?.active;

subscribe(listener)

Registers a listener that receives { snapshot } on every state change. Fires immediately with the current snapshot. Returns an unsubscribe function.
const unsub = toolbar.subscribe(({ snapshot }) => {
  // update your UI
});

execute(id, payload?)

Runs the command identified by id. Returns true if the command executed, false otherwise. Automatically restores focus to the editor.
toolbar.execute('bold');                    // toggle
toolbar.execute('font-size', '14pt');       // set value
toolbar.execute('table-insert', { rows: 3, cols: 4 }); // with object payload

destroy()

Tears down event listeners and clears all subscriptions. Call this when unmounting your toolbar.
toolbar.destroy();

ToolbarSnapshot

context
ToolbarContext | null
The current editing context. null when no editor is active.
commands
Partial<Record<PublicToolbarItemId, ToolbarCommandState>>
Map of command IDs to their current state.

ToolbarCommandState

active
boolean
Whether the command is currently active (e.g., bold is on at the cursor position).
disabled
boolean
Whether the command can run right now. Disabled when the editor isn’t editable or the command doesn’t apply to the current selection.
value
unknown
The current value for commands that have one (font size, text color, zoom, etc.). Not present for toggle commands like bold.

ToolbarContext

target
ToolbarTarget
The primary execution surface. Use target.commands for direct command access when execute() doesn’t cover your use case.
surface
'body' | 'header' | 'footer'
Which document surface is currently active.
isEditable
boolean
Whether the editor is in an editable state.
selectionEmpty
boolean
Whether the current selection is collapsed (cursor with no range).

Command reference

Snapshot values match the format you pass to execute(). What you read is what you write — no conversion needed.

Text formatting

CommandPayloadvalue in snapshot
bold
italic
underline
strikethrough
clear-formatting
copy-format

Font controls

CommandPayloadvalue in snapshot
font-size'12pt''12pt'
font-family'Arial, sans-serif''Arial, sans-serif'
text-color'#ff0000' or 'none''#ff0000'
highlight-color'#ffff00' or 'none''#ffff00'

Paragraph

CommandPayloadvalue in snapshot
text-align'left' | 'center' | 'right' | 'justify'alignment string
line-heightnumber (e.g. 1.5)number
linked-stylestyle object from helpersstyle ID string
bullet-list
numbered-list
indent-increase
indent-decrease

Insert

CommandPayloadvalue in snapshot
link{ href: string | null }href string or null
image— (opens file picker)
table-insert{ rows: number, cols: number }

Table actions

CommandPayloadvalue in snapshot
table-add-row-before
table-add-row-after
table-delete-row
table-add-column-before
table-add-column-after
table-delete-column
table-delete
table-merge-cells
table-split-cell
table-remove-borders
table-fix

Document

CommandPayloadvalue in snapshot
undo
redo
ruler
zoomnumber (e.g. 125)number
document-mode'editing' | 'suggesting' | 'viewing'mode string

Track changes

CommandPayloadvalue in snapshot
track-changes-accept-selection
track-changes-reject-selection

Constants

headlessToolbarConstants provides preset option arrays for dropdown-style controls. Each option has { label, value } — use value when calling execute().
import { headlessToolbarConstants } from 'superdoc/headless-toolbar';

const { DEFAULT_FONT_SIZE_OPTIONS } = headlessToolbarConstants;
// [{ label: '8', value: '8pt' }, { label: '9', value: '9pt' }, ...]
ConstantContents
DEFAULT_FONT_FAMILY_OPTIONSAptos, Georgia, Arial, Courier New, Times New Roman
DEFAULT_FONT_SIZE_OPTIONS8pt through 96pt (14 sizes)
DEFAULT_TEXT_ALIGN_OPTIONSleft, center, right, justify
DEFAULT_LINE_HEIGHT_OPTIONS1.00, 1.15, 1.50, 2.00, 2.50, 3.00
DEFAULT_ZOOM_OPTIONS50% through 200% (7 levels)
DEFAULT_DOCUMENT_MODE_OPTIONSediting, suggesting, viewing (with descriptions)
DEFAULT_TEXT_COLOR_OPTIONS13 colors including none
DEFAULT_HIGHLIGHT_COLOR_OPTIONS8 colors including none

Helpers

headlessToolbarHelpers provides utilities for advanced workflows.
import { headlessToolbarHelpers } from 'superdoc/headless-toolbar';
HelperWhat it does
getQuickFormatList(editor)Returns available paragraph styles (Normal, Heading 1, etc.) for a style dropdown.
generateLinkedStyleString(style, ...)Returns inline CSS for previewing a paragraph style in your UI.
getFileOpener()Returns a function that opens a file picker. Most consumers should use execute('image') instead.
processAndInsertImageFile(...)Processes and inserts an image file into the editor. Most consumers should use execute('image') instead.

React example

A minimal hook that wires the headless toolbar to React state:
import { useEffect, useRef, useState } from 'react';
import { createHeadlessToolbar } from 'superdoc/headless-toolbar';

function useHeadlessToolbar(superdoc, commands) {
  const [snapshot, setSnapshot] = useState({ context: null, commands: {} });
  const controllerRef = useRef(null);

  useEffect(() => {
    const toolbar = createHeadlessToolbar({ superdoc, commands });
    controllerRef.current = toolbar;
    const unsub = toolbar.subscribe(({ snapshot: s }) => setSnapshot(s));
    return () => { unsub(); toolbar.destroy(); };
  }, [superdoc]);

  return {
    snapshot,
    execute: (id, payload) => controllerRef.current?.execute(id, payload),
  };
}
Then in your component:
function Toolbar({ superdoc }) {
  const { snapshot, execute } = useHeadlessToolbar(superdoc, [
    'bold', 'italic', 'underline', 'font-size',
  ]);

  return (
    <div>
      <button
        onClick={() => execute('bold')}
        data-active={snapshot.commands['bold']?.active}
        disabled={snapshot.commands['bold']?.disabled}
      >
        Bold
      </button>
    </div>
  );
}

Examples

Framework examples

Full examples with React + shadcn, React + MUI, Vue + Vuetify, Svelte, and vanilla JS