Documentation Index
Fetch the complete documentation index at: https://superdoc-caio-sd-2929-configurable-toolbar.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
superdoc/ui/react is sugar over a controller. If you are not using React, talk to the controller directly. The hooks just call its methods on your behalf.
When to use this page
| You are… | Read |
|---|---|
| Building with React | React setup. Skip this page. |
| Building with Vue, Svelte, Angular, or vanilla | This page. |
| Building a framework adapter | This page is the contract. |
Install
Create the controller
createSuperDocUI({ superdoc }) runs once per editor mount and returns a controller. Hand it the SuperDoc instance.
ui is null-safe to keep around until your app tears down. Call ui.destroy() on unmount.
Bind state with observe
Domain handles emit throughobserve(snapshot => ...). The listener fires once synchronously with the current snapshot, then again on every change. Returns an unsubscribe.
ui.toolbar.observe, ui.selection.observe, ui.trackChanges.observe, ui.document.observe. Per-command state binds the same way: ui.commands.bold.observe(state => ...).
A wrapped subscribe(({ snapshot }) => ...) form is also exported. Either works; pick one and stay consistent.
Group teardown with createScope
WithoutuseEffect cleanup, you have to track every unsubscribe yourself. ui.createScope() does that for you.
ui.destroy() cascades into every live scope, so a typical app needs only one teardown call:
Register custom commands
scope.register(...) is the same as ui.commands.register(...) but the scope auto-unregisters on tear-down. The registration is reachable through the same ui.commands.<id> and ui.commands.get(id) paths as built-ins.
Validate config-driven command ids
If your toolbar reads ids from a config file or feature flag, validate at startup.ui.commands.has(id) is the cheap check; ui.commands.require(id) throws on unknown ids at trusted dispatch sites.
BUILT_IN_COMMAND_IDS is the readonly list of every valid built-in id. PublicToolbarItemId is the matching type.
Tiny skeleton
The whole picture in one file:What ships
| Surface | Purpose |
|---|---|
createSuperDocUI({ superdoc }) | One controller per editor mount |
ui.createScope() | Lifecycle bag for subscriptions, registrations, DOM listeners |
ui.<domain>.observe(snapshot => ...) | Read state. Domains: toolbar, commands.<id>, comments, trackChanges, selection, document |
ui.<domain>.<action>(...) | Mutate. Examples: ui.comments.resolve(id), ui.trackChanges.accept(id), ui.document.setMode('suggesting') |
ui.commands.has(id) / require(id) | Validate config-driven ids |
BUILT_IN_COMMAND_IDS | Readonly list of every built-in command id |
ui.destroy() | Teardown. Cascades into every live scope. |
Common pitfalls
Forgetting to call ui.destroy()
Forgetting to call ui.destroy()
Without
ui.destroy(), internal listeners and any live scope keep running after your app unmounts. Hot-reload sessions accumulate dead controllers. Always call it on unload and on every framework-specific destroy hook (onScopeDispose in Vue, onDestroy in Svelte, DestroyRef in Angular).Reading the live selection at submit time
Reading the live selection at submit time
A composer that reads
ui.selection.getSnapshot() at submit time will see null if the user typed in a textarea between opening the composer and pressing Send. Capture the selection at composer-open with ui.selection.capture() and pass the snapshot into ui.comments.createFromCapture(capture, { text }).Built-in UI overlapping with yours
Built-in UI overlapping with yours
Pass
modules: { comments: false } to new SuperDoc(...) to disable the built-in comment bubble. Same shape for tracked changes. Document-level features (DOCX import/export, comments round-trip) keep working.
