Skip to main content

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.

The provider holds one controller per editor mount. Components read it through useSuperDocUI() and the per-domain hooks. Drop your toolbar, sidebar, and review components anywhere inside the provider.

Install

pnpm add superdoc @superdoc-dev/react

Wire the provider

Wrap your app in <SuperDocUIProvider>. Mount <SuperDocEditor> somewhere inside. Hand the SuperDoc instance to the provider on onReady.
import { SuperDocEditor } from '@superdoc-dev/react';
import '@superdoc-dev/react/style.css';
import { SuperDocUIProvider, useSetSuperDoc } from 'superdoc/ui/react';

import { Toolbar } from './Toolbar';
import { ActivitySidebar } from './ActivitySidebar';

export function App() {
  return (
    <SuperDocUIProvider>
      <Toolbar />
      <EditorMount />
      <ActivitySidebar />
    </SuperDocUIProvider>
  );
}

function EditorMount() {
  const setSuperDoc = useSetSuperDoc();

  return (
    <SuperDocEditor
      document="/contract.docx"
      hideToolbar
      contained
      onReady={({ superdoc }) => setSuperDoc(superdoc)}
    />
  );
}
hideToolbar and contained keep <SuperDocEditor> from rendering its built-in toolbar or claiming the page. Your components own the layout.

Read state from a hook

Hooks return live state. The component re-renders only when the slice it subscribed to changes.
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>
  );
}

What’s available

HookReturnsUse it for
useSuperDocUI()The controller, or null until readyCalling ui.commands.get(id)?.execute() and other actions
useSuperDocCommand(id){ active, disabled, value, source }Binding one toolbar button to one command
useSuperDocComments()The live comments feedBuilding a comments sidebar
useSuperDocTrackChanges()The live tracked-changes feedBuilding a track-changes review panel
useSuperDocSelection()The current selection sliceEnabling/disabling buttons, building bubble menus
useSuperDocDocument(){ ready, mode }Mode toggles, “ready” guards
useSuperDocSlice(picker, initial)Any slice from ui.select(...)Custom subscriptions the typed hooks don’t cover
useSuperDocHost()The host SuperDoc instanceOperations the controller doesn’t bridge yet
useSetSuperDoc()The setter the editor mount calls in onReadyOnly the editor mount component

What the provider does

createSuperDocUI({ superdoc }) runs once when the editor reports ready. It tears down on unmount. You don’t manage the lifecycle.
Components that render before the editor is ready see useSuperDocUI() return null. Guard with if (!ui) return null; or render a pending state.

Common pitfalls

Set modules: { comments: false } on <SuperDocEditor> to disable SuperDoc’s built-in comment bubble. Same pattern for modules: { trackChanges: { ... } } if you want the engine without rendering. The Document API and exports keep working either way.
useSuperDocUI() returns null between mount and onReady. Hooks downstream (useSuperDocCommand, useSuperDocComments, etc.) return their fallback states during that window. Your buttons render disabled, your sidebar shows empty. No flicker, no errors.
One <SuperDocUIProvider> holds one controller. If you render two editors on a page, give each its own provider.