Keep custom controls in sync
Render selection-aware controls and confirm what each command did.
The Bold button from the first control guide changes as you move the selection. It does not inspect the document DOM. It renders command state from the Editor, runs the command, and handles the result.
Follow one control through the loop
Choose each sample selection below, then press Bold when it is available. This simulation isolates the state your control renders and the result it handles. The first control guide runs the same loop in a real Editor.
Choose sample content, then press Bold. The selection is simulated so each state is immediate. A real controller derives the same values from the active Editor selection.
- enabled
- true
- active
- false
- reason
- undefined
- result
Every custom control repeats the same four steps:
- Read the command's current state.
- Render that state in your control.
- Run the command from the user's action.
- Inspect the result before reporting success or starting dependent work.
Render the current state
Each command exposes a small state object:
| Field | What your control should do |
|---|---|
enabled | Disable the action when it cannot run |
active | Show whether a toggle is applied |
value | Show the current value for a picker |
reason | Explain why an action is disabled |
supported | Check whether the controller recognizes and can route the command |
Use getState() for the initial value and observe() for later changes. In React, useSuperDocCommand(id) does both
and rerenders the component when the state changes.
Do not derive state from the rendered document DOM. Selection, mode, history, and document content can all change whether a command is available.
Disable a control when enabled is false. Show reason when it helps someone recover. Remove the control only when
the workflow does not need it.
Observe Undo and Redo
ui.commands.get('undo').observe(listener) and the corresponding redo handle
report command availability. Retain and call both returned unsubscribe functions
when the host control is removed. Use doc.history.get() when you need
undoDepth, redoDepth, canUndo, and canRedo after an awaited mutation.
Command state describes whether an action can run. It is not an undo-stack-depth subscription: adding a second entry can leave Undo enabled with the same command state. Applications that display a stack count must read history after their operations; command observation alone does not promise notification for every depth change.
Confirm the command result
Use executeAsync() when your interface or later work depends on the action:
| Result | Meaning |
|---|---|
false | The controller could not route the command |
true | The host accepted the command without a receipt, including no-ops |
{ success: true } | The document operation completed |
{ success: false } | The receipt explains why the operation did not finish |
Command state is a snapshot. Selection-dependent commands can refresh it before execution, so a stale disabled
snapshot does not always mean execution will fail. Keep controls disabled according to enabled, and check the result even when enabled was true.
A settled true does not guarantee a document mutation. For example, Undo with an empty history can return true as an
accepted no-op. For receipt failure codes, see
Receipts and errors.
Choose commands deliberately
Use BuiltInCommandId when a control accepts only SuperDoc commands. Use CommandId when it can also accept commands
registered by your application. Both types are exported from superdoc/ui.
ui.commands.ids lists the commands known to the current controller, and ui.commands.has(id) checks one ID. Use those
methods for discovery or capability checks. Choose the actions your workflow needs instead of generating a toolbar from
the entire list.
Next, build a custom toolbar that applies this loop to Bold, font, and size. If you do not need to own the toolbar's markup or interaction design, configure the built-in toolbar instead.