Custom UI overview
Build application-owned controls over the public SuperDoc v2 UI controller.
A custom UI keeps SuperDoc's DOCX canvas and editing behavior while your application owns the toolbar, panels, and workflow around it.
Use this approach when a general-purpose document toolbar does not match the product task. A contract approval screen, structured intake flow, or focused review experience may need a smaller set of controls and application-specific state.
Read the controller off the instance
Every SuperDoc instance exposes its own UI controller as editor.ui. It is the shortest path to custom controls and the only path available when SuperDoc is loaded through a classic <script> tag, where package subpath imports are unavailable.
const editor = new SuperDoc({ selector: '#editor', document: file });
const stop = editor.ui.comments.observe((comments) => renderSidebar(comments.items));
const bold = editor.ui.commands.get('bold');Four properties of editor.ui matter when you wire an application to it:
- One canonical controller. Every SuperDoc instance owns one controller at
editor.ui. SuperDoc's own toolbar, popovers, and keyboard routing read that same object, so your controls and the built-in ones never disagree about command state. - Stable identity. Reading it twice returns the same controller. Replacing the document or switching the active editor in a multi-document instance does not replace it.
- Safe before the document is ready. Slices report
pendingrather than throwing, so you can subscribe in the same tick as the constructor. - SuperDoc destroys it.
editor.destroy()tears the controller down.editor.uiis typedBorrowedSuperDocUI, which omitsdestroy(), so you cannot accidentally tear down state that every other consumer is observing. Release your own subscriptions and leave the controller alone.
editor.ui is an observation and command surface, not a permission boundary. Anything it exposes is already reachable by the page hosting SuperDoc.
Construct a separate controller
createSuperDocUI from superdoc/ui is an ownership escape hatch, not the normal path. It binds to a SuperDoc instance and exposes the same surface, but you own its lifecycle: you create it and you call its destroy().
Mounting and unmounting an ordinary panel is not a reason to build one. A panel can release its own subscriptions and keep sharing editor.ui, which is less to own and less to get wrong. Reach for the factory when a consumer genuinely needs a controller whose lifetime is independent of the Editor's — a detached preview driven from its own state, for example.
Destroying a controller you built this way tears down only that controller. editor.ui, the built-in toolbar, and every other consumer keep working, so an independent lifecycle is a supported choice rather than a risky one.
Prefer editor.ui everywhere else.
The controller provides:
- Immutable snapshots for selection, toolbar, comments, tracked changes, content controls, fonts, zoom, document state, styles, and search.
- Subscriptions that update controls when editor state changes.
- Command handles with
enabled,active,value, and stable disabled reasons. - Async command execution with receipts or explicit failure results.
- Selection capture, viewport navigation, and document-aware workflow helpers.
- One
destroy()boundary for controller subscriptions and listeners.
Both forms expose the same surface. The rest of this section describes that surface without repeating which one you hold.
superdoc/ui/react provides the same controller through a provider and hooks. Use the framework-neutral controller when your application does not use React or when UI state belongs outside a component tree.
Treat snapshots as the source of control state
Do not infer whether a button is enabled from DOM selection or local UI assumptions. Read the command or slice snapshot and subscribe to changes.
For example, a Bold button should use the bold command handle's state. The controller decides whether the current selection can be formatted and whether bold is already active.
The model below shows the three states your control must handle. Choose each sample selection, then toggle Bold when the command is enabled. This model does not run a SuperDoc instance. The controller setup on the next page connects the same state contract to a real Editor.
Choose sample content to see the state a custom Bold control receives. The selection is simulated. A real controller derives these values from the active Editor selection.
- enabled
- true
- active
- false
- reason
- undefined
Await meaningful actions
Use executeAsync() when later work depends on the command result. Browser-backed document operations may settle asynchronously and can return a Document API receipt.
Do not treat the absence of an exception as success. Inspect a returned receipt before saving, navigating, or starting a dependent action.
Clean up what you own
Release every subscription your controls created, then destroy the SuperDoc instance when the application surface unmounts. That also destroys editor.ui.
A controller you built yourself with createSuperDocUI() is the one exception: destroy it before the instance, because nothing else will.
Continue with Custom UI controller setup to build a working Bold control against a real DOCX. React applications can then move to the React setup page once that shared lifecycle is clear.