useSuperDocCommand(id) subscribes one button to one command. The component re-renders when that command flips active or disabled, never on every editor change. ui.commands.get(id)?.execute(payload?) runs the command.
A single button
A row of buttons
Build a static config and map. Each<ToolbarButton> subscribes to its own command, so unrelated state changes don’t re-render the row.
disabled / active straight from the controller.
Commands with payloads
Some commands take a value. Pass it toexecute().
useSuperDocCommand('font-size').value is the current value for the active selection. The same pattern works for font-family, text-color, and other value commands.
Font family picker
UseuseSuperDocFontOptions() for a custom font dropdown. It returns the fonts SuperDoc can render plus fonts used by the active document, sorted alphabetically. Without a configured font pack that is the conservative baseline; configure the pack (@superdoc-dev/fonts, or fonts.assetBaseUrl) and it returns the full set, minus anything curated with createSuperDocFonts.
label is what you show. value is what you pass to the font-family command. previewFamily is only for rendering the option row.
Interactive UI vs explicit document edits
Use command execution for a toolbar. It preserves the active editor selection and matches the built-in toolbar behavior.format.apply call.
What the hook returns
| Field | Type | Meaning |
|---|---|---|
active | boolean | The command is “on” for the current selection (cursor is inside bold text). |
disabled | boolean | The command can’t run right now (no selection, wrong document mode). |
value | unknown | The command’s current value when applicable (font name, size, color). |
source | 'built-in' | 'custom' | Where the command came from. |
useSuperDocCommand returns a fallback { active: false, disabled: true, source: 'built-in' } while the editor is initializing, so your buttons render disabled with no flicker.
Built-in command ids
Common ids you’ll wire to buttons:| Group | Ids |
|---|---|
| Text | bold, italic, underline, strikethrough |
| Inline | link, font-family, font-size, text-color, highlight-color |
| Layout | text-align, line-height, indent-increase, indent-decrease |
| Lists | bullet-list, numbered-list |
| Style | linked-style, clear-formatting, copy-format |
| History | undo, redo |
| Tracked changes | track-changes-accept-selection, track-changes-reject-selection |
| View | ruler, zoom, zoom-fit-width, document-mode |
| Tables | table-insert, table-add-row-before, table-add-row-after, table-delete-row, table-add-column-before, table-add-column-after, table-delete-column, table-merge-cells, table-split-cell, table-delete |
| Insert | image |
PublicToolbarItemId in superdoc/ui is the source of truth. Anything you can pass to createHeadlessToolbar({ commands }) works as a useSuperDocCommand id.
Aggregate snapshots
Need every command in one render pass (e.g. you generate the toolbar from the active context)? Subscribe to the whole toolbar slice.useSuperDocCommand per button when you can. useSuperDocToolbar re-renders on any command change.
Trade-offs
- The hook subscribes per-id. Reusing a button component with different ids is fine: the subscription resets when
idchanges. - Built-in command state is derived from the active editor. If your provider sits above multiple editors, the ids you pass are scoped to whichever editor reported ready last.
- Custom commands you registered with
ui.commands.registerwork with the same hook.

