Skip to main content
Framework-agnostic patterns built on createHeadlessToolbar(). Pick the one closest to your stack.
On React? Reach for Custom UI › Toolbar and commands instead. The typed React surface (useSuperDocCommand, ui.commands.register) re-renders one button per command, ships with selection / comments / track-changes hooks, and works with any design system (shadcn, MUI, Mantine, Tailwind). The headless examples below are for non-React stacks.

Vue + Vuetify

Vertical sidebar panel. Navigation drawer with expansion panels grouping related controls. Stack: Vue 3, Vuetify 3, Material Design Icons
<v-navigation-drawer permanent width="260">
  <v-expansion-panels>
    <v-expansion-panel title="Text">
      <template #text>
        <v-btn-toggle v-model="activeFormats" multiple>
          <v-btn icon="mdi-format-bold" @click="execute('bold')" />
          <v-btn icon="mdi-format-italic" @click="execute('italic')" />
          <v-btn icon="mdi-format-underline" @click="execute('underline')" />
        </v-btn-toggle>

        <v-select
          label="Font size"
          :items="fontSizes"
          @update:model-value="v => execute('font-size', v)"
        />
      </template>
    </v-expansion-panel>
  </v-expansion-panels>
</v-navigation-drawer>

Full source

Vue + Vuetify example

Svelte + Tailwind

Fixed bottom bar. Compact, mobile-inspired layout with Svelte 5 runes for reactivity. Stack: Svelte 5, Tailwind CSS, Lucide Svelte
<script>
  let snapshot = $state(toolbar.getSnapshot());

  $effect(() => {
    return toolbar.subscribe(({ snapshot: s }) => {
      snapshot = s;
    });
  });
</script>

<div class="fixed bottom-0 inset-x-0 flex items-center gap-1 px-3 h-12 bg-white border-t">
  <button
    class:active={snapshot.commands.bold?.active}
    onclick={() => toolbar.execute('bold')}
  >
    <Bold size={16} />
  </button>
  <button
    class:active={snapshot.commands.italic?.active}
    onclick={() => toolbar.execute('italic')}
  >
    <Italic size={16} />
  </button>
</div>

Full source

Svelte + Tailwind example

Vanilla JS

Zero-framework proof. Plain buttons, focus-preserving menus, DOM manipulation. No build step required. Stack: No framework, plain HTML/CSS/JS, Lucide
// Sync button states from toolbar snapshot
toolbar.subscribe(({ snapshot }) => {
  for (const btn of document.querySelectorAll('[data-cmd]')) {
    const cmd = snapshot.commands[btn.dataset.cmd];
    btn.classList.toggle('active', cmd?.active);
    btn.disabled = cmd?.disabled;
  }
});

// Single event listener via delegation
document.querySelector('#toolbar').addEventListener('click', (e) => {
  const cmd = e.target.closest('[data-cmd]')?.dataset.cmd;
  if (cmd) toolbar.execute(cmd);
});
<!-- Markup -->
<div id="toolbar">
  <button data-cmd="bold"><b>B</b></button>
  <button data-cmd="italic"><i>I</i></button>
  <button data-cmd="underline"><u>U</u></button>
</div>
For font family, font size, and other selection commands, use a button menu that prevents mousedown focus changes before executing the command. Native selects can move browser focus and visually clear the editor selection while their menu is open.

Full source

Vanilla JS example

Running the examples

cd examples/advanced/headless-toolbar/<example-name>
pnpm install
pnpm dev
Replace <example-name> with vue-vuetify, svelte-shadcn, or vanilla.

Next steps

Custom UI (React)

The recommended path for React apps. Typed hooks, custom commands, selection-aware buttons.

Headless toolbar API reference

Full command table, snapshot shape, and helper utilities.