> ## Documentation Index
> Fetch the complete documentation index at: https://docs.superdoc.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Toolbar and commands

> Bind buttons to bold, italic, lists, undo, redo, and every other built-in command.

`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

```tsx theme={null}
import { useSuperDocCommand, useSuperDocUI } from 'superdoc/ui/react';

export function BoldButton() {
  const ui = useSuperDocUI();
  const bold = useSuperDocCommand('bold');

  return (
    <button
      className={bold.active ? 'active' : ''}
      disabled={bold.disabled}
      onClick={() => ui?.commands.get('bold')?.execute()}
    >
      B
    </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.

```tsx theme={null}
import { useSuperDocCommand, useSuperDocUI } from 'superdoc/ui/react';

const TEXT_BUTTONS = [
  { id: 'bold', label: 'B', title: 'Bold (⌘B)' },
  { id: 'italic', label: 'I', title: 'Italic (⌘I)' },
  { id: 'underline', label: 'U', title: 'Underline (⌘U)' },
];

export function Toolbar() {
  return (
    <div role='toolbar'>
      {TEXT_BUTTONS.map((b) => (
        <ToolbarButton key={b.id} id={b.id} label={b.label} title={b.title} />
      ))}
    </div>
  );
}

function ToolbarButton({ id, label, title }: { id: string; label: string; title: string }) {
  const ui = useSuperDocUI();
  const cmd = useSuperDocCommand(id);

  return (
    <button
      className={cmd.active ? 'active' : ''}
      disabled={cmd.disabled}
      title={title}
      onClick={() => ui?.commands.get(id)?.execute()}
    >
      {label}
    </button>
  );
}
```

See the [configurable toolbar example](https://github.com/superdoc-dev/superdoc/tree/main/examples/editor/custom-ui/configurable-toolbar) for a runnable vanilla version: a config-driven button row that subscribes per-id and reads `disabled` / `active` straight from the controller.

## Commands with payloads

Some commands take a value. Pass it to `execute()`.

```tsx theme={null}
import { useSuperDocCommand, useSuperDocFontSizeOptions, useSuperDocUI } from 'superdoc/ui/react';

function FontSizePicker() {
  const ui = useSuperDocUI();
  const size = useSuperDocCommand('font-size');
  const options = useSuperDocFontSizeOptions();

  return (
    <select
      value={typeof size.value === 'string' ? size.value : ''}
      disabled={size.disabled}
      onChange={(e) => ui?.commands.get('font-size')?.execute(e.target.value)}
    >
      {options.map((option) => (
        <option key={option.value} value={option.value}>
          {option.label}
        </option>
      ))}
    </select>
  );
}
```

`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

Use `useSuperDocFontOptions()` 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.

```tsx theme={null}
import { useEffect, useRef, useState } from 'react';
import type { SelectionCapture } from 'superdoc/ui';
import { useSuperDocCommand, useSuperDocFontOptions, useSuperDocUI } from 'superdoc/ui/react';

function firstFontName(value: unknown) {
  return typeof value === 'string'
    ? value
        .split(',')[0]
        ?.trim()
        .replace(/^["']|["']$/g, '') || ''
    : '';
}

function FontFamilyPicker() {
  const ui = useSuperDocUI();
  const font = useSuperDocCommand('font-family');
  const options = useSuperDocFontOptions();
  const capturedSelection = useRef<SelectionCapture | null>(null);
  const menuRef = useRef<HTMLDivElement | null>(null);
  const [open, setOpen] = useState(false);
  const current = firstFontName(font.value).toLowerCase();
  const selectedOption =
    options.find((option) => {
      return (
        firstFontName(option.value).toLowerCase() === current ||
        firstFontName(option.previewFamily).toLowerCase() === current
      );
    }) ?? null;

  useEffect(() => {
    if (!open) return;
    const close = (event: MouseEvent) => {
      if (!menuRef.current?.contains(event.target as Node | null)) setOpen(false);
    };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, [open]);

  const rememberSelection = () => {
    const capture = ui?.selection.capture();
    if (capture) capturedSelection.current = capture;
  };

  const applyFont = (value: string) => {
    if (!ui) return;
    if (capturedSelection.current) {
      ui.selection.restore(capturedSelection.current);
      capturedSelection.current = null;
    }
    setOpen(false);
    ui.toolbar.execute('font-family', value);
  };

  return (
    <div ref={menuRef}>
      <button
        type='button'
        disabled={font.disabled}
        onMouseDown={(event) => {
          event.preventDefault();
          rememberSelection();
        }}
        onClick={() => setOpen((value) => !value)}
      >
        {selectedOption?.label ?? 'Font'}
      </button>
      {open && (
        <div role='listbox'>
          {options.map((option) => (
            <button
              key={option.value}
              type='button'
              role='option'
              aria-selected={option.value === selectedOption?.value}
              style={{ fontFamily: option.previewFamily }}
              onMouseDown={(event) => {
                event.preventDefault();
                rememberSelection();
              }}
              onClick={() => applyFont(option.value)}
            >
              {option.label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}
```

When a user picks Calibri, SuperDoc stores and exports Calibri. The preview can render with the bundled fallback that actually paints it.

Avoid native selects for this control. They take browser focus and visually clear the editor selection while the menu is open.

## 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.

```tsx theme={null}
ui?.toolbar.execute('font-family', option.value);
ui?.toolbar.execute('font-size', option.value);
```

Use the Document API when your code already has a target and wants one explicit document mutation. Batch related formatting in one `format.apply` call.

```ts theme={null}
editor.doc.format.apply({
  target,
  inline: {
    fontFamily: 'Inter',
    fontSize: 15,
  },
});
```

## 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.

```tsx theme={null}
import { useSuperDocToolbar } from 'superdoc/ui/react';

function ContextualToolbar() {
  const toolbar = useSuperDocToolbar();
  const ids = Object.keys(toolbar.commands);

  return <div>{ids.length} commands available</div>;
}
```

Prefer `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 `id` changes.
* 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.register`](/editor/custom-ui/custom-commands) work with the same hook.
