Hooks
Connect React controls to DOCX editor state and commands with hooks.
Call these hooks from components inside <DocxEditor.Root>. The packaged <DocxEditor> provides that root for its children.
Connect a command button
This button runs a toolbar command and shows why the command cannot run:
import { useEditorCommand } from '@docx-editor.dev/react';
function BoldButton() {
const bold = useEditorCommand('text.bold');
return (
<button
onMouseDown={(e) => e.preventDefault()}
onClick={() => bold.execute()}
disabled={!bold.isEnabled}
data-active={bold.isActive || undefined}
title={bold.disabledReason ?? 'Bold'}
>
B
</button>
);
}useEditorCommand(target: ChromeSlotId | EditorCommand): EditorCommandState takes a slot ID from the chrome slot reference, or a raw EditorCommand such as { type: 'setEditingMode', mode: 'suggesting' }.
| Member | Type | Description |
|---|---|---|
execute(options?) | boolean | Runs the command. Returns true when it applied. |
isActive | boolean | Command state at the caret. |
isEnabled | boolean | Whether the command can run. |
disabledReason | string | null | Why the command cannot run. |
value | string | null | Current value for value slots. |
Read state and events
useEditorState subscribes to one slice of the snapshot. The component renders only when that slice changes:
import { useEditorState } from '@docx-editor.dev/react';
function PageIndicator() {
const page = useEditorState((s) => s.page);
return (
<span>
{page.current} / {page.total}
</span>
);
}Pass a comparator for an object slice. Common fields include page, selection, formatting, editable, isLoading, editingMode, canUndo, pageSetup, and lastRejection.
| Hook | Returns | Notes |
|---|---|---|
useEditorState(selector, isEqual?, options?) | T | Selected snapshot value. |
useDocxEditor() | DocxEditorInstance | null | null before creation and outside a root. |
useEditorEvent(event, handler) | void | Subscribes for the component lifetime. |
useEditorSnapshot(editor) | number | Revision counter for useSyncExternalStore. |
useEditorCaret() | { paragraphId, offset } | null | Caret for at parameters. |
useEditorHostDocument() | Document | null | Document that holds the pages, also in a pop-out window. null on the server. |
useEditorColorMode() | { colorMode } | 'light' or 'dark'. 'light' outside a root. |
selectionChange also fires when other state moves without a document change, such as the page count. Compare snapshot.selection with the previous value before you react.
useDocxEditor
Use the instance for actions and one-time reads:
import { useDocxEditor } from '@docx-editor.dev/react';
function SaveButton({ upload }: { upload: (bytes: ArrayBuffer) => Promise<void> }) {
const editor = useDocxEditor();
return (
<button
disabled={!editor}
onClick={async () => {
const bytes = await editor?.save();
if (bytes) void upload(bytes);
}}
>
Save
</button>
);
}Calling editor.snapshot() during render does not subscribe. For scrollToAnchor and highlightAnchor, see Navigation.
Choose a hook
Formatting and page setup
| Hook | Returns |
|---|---|
useFontFamily() | { value, options, setValue, isEnabled } |
useParagraphStyle() | { value, options, setValue, isEnabled } for paragraph styles |
useParagraphFormat() | { format, apply, isEnabled } |
useParagraphIndent() | { indent, apply, isEnabled } |
usePageSetup() | { pageSetup, apply, isEnabled } |
useEditorValueCommand(slotId) | { value, options, execute, isEnabled, disabledReason }. See Apply a value. |
useZoom() | { zoom, mode, levels, isFit, canZoomIn, canZoomOut, setZoom, setMode, zoomIn, zoomOut, reset, fitToWidth, auto } |
apply sends one command and returns true when it applied.
apply input | Result |
|---|---|
| Omitted field | Stays unchanged. |
null on spacing or indent | Clears the local value. |
0 | Sets an explicit zero. |
In useParagraphFormat().format, a field is null when the selected paragraphs differ. usePageSetup uses twips for sizes and margins, and scope takes 'section' or 'document'.
useZoom
For a complete zoom control, see Zoom.
Document content
| Hook | Returns |
|---|---|
useWatermark() | { watermark, apply, read, isEnabled } |
useBookmarks() | { bookmarks, insert, remove, goTo, keepFirst, isWritable } |
useGoToBookmark(options?) | { query, setQuery, rows, bookmarks, activeIndex, setActiveIndex, move, go, problem, problemName, sortBy, setSortBy, showHidden, setShowHidden } |
useCommentDraftRequest(handler) | void |
useWatermark
apply sends one setWatermark command, so each call is one undo step. Pass null to remove the watermark. read() returns the current watermark as a setWatermark value, including picture bytes.
useBookmarks
Each action sends one command and returns ExecResult. goTo works in a read-only document; isWritable reports whether edits are accepted.
Each name has one entry; duplicates counts copies, and keepFirst(name) removes them without moving the selection.
useGoToBookmark({ enabled }) reads bookmarks only while your UI is open. For details, see Bookmarks.
go(name?) targets name, then the highlighted row. For a nonempty query, it then tries an exact name match, ignoring case, then the first result. It returns ExecResult.
useCommentDraftRequest
Register this handler for your own comment UI. review.addComment stays disabled until a handler is registered, and the latest registration wins. Pass null while your UI cannot open a draft.
The handler takes no arguments. Read the selection with editor.getSelectionPlacement(), and pin it with editor.retainSelection() while the draft is open.
Navigation and search
| Hook | Returns |
|---|---|
useDocumentOutline() | { items, headings, selectedBlockId, goTo, isEmpty } |
useDocumentSearch(options?) | { query, setQuery, find, matches, activeIndex, activeMatch, next, previous, goTo, clear, isPending, truncated, matchCase, setMatchCase, wholeWord, setWholeWord } |
useHighlights(name, source, options?) | { applied, unavailable } |
useHighlightAt(name?) | Highlight under the pointer, or null |
useNavigationPane(options?) | { open, setOpen, toggle, tab, setTab, paneWidth, shift, overlay } |
useNavigationShift() | number, the page offset for an open pane |
Each outline item is { heading, depth }, and depth counts from the shallowest heading.
setQuery waits 150 milliseconds; find searches at once. truncated means the search stopped at 2,000 matches. For examples, see Navigation.
useHighlights
A function source reruns after document changes, so wrap it in useCallback. For options, see Highlight text ranges.
Loading, fonts, and locale
| Hook | Returns |
|---|---|
useDocxSource(source, options?) | { document, fonts, error, isLoading } |
useFonts(source, ...fragments) or useFonts(...origins) | MarkedFontResolver, stable across renders |
useOpenFailure(options?) | { failure, message, dismiss } |
useTranslation() | { t, catalogue, lang } |
useChromeTranslate(overrides?) | ChromeTranslate that checks the override Map first |
useChromeDir() | 'ltr' | 'rtl' | undefined |
useDocxSource
useDocxSource fetches the document and its fonts. It accepts a URL string, URL, Uint8Array, or ArrayBuffer, and cancels on change.
For font origins, see Fonts. For refused opens, see Open failures.
Review
| Hook | Returns |
|---|---|
useReviewAuthors() | readonly ReviewAuthorInfo[], tracked-change authors first |
useReviewGutter() | { inlineStart, inlineEnd } reserved by the review rail |
@docx-editor.dev/pro/react adds useReview, useReviewOf, useReviewItem, useReviewAuthor, useStackedReviewPositions, and useReviewRailMetrics. See Tracked changes.
Popovers, scopes, and toolbar helpers
| Hook | Returns |
|---|---|
useHyperlinkPopup() | { state, open, openAtCaret, close, beginEdit, commitEdit, setUrl, setText, copy, unlink, openTarget } |
useHyperlinkPopupInstance(active?) | Same, outside the owning part |
useContentControl() | { control, controls, setValue, remove, formFill, ... } |
useContentControlInstance() | Same, outside the owning part |
useContentControlWidget() | Value popup draft, calendar, and actions. selectDay(iso) and selectToday() apply the date at once. See Compose the value popup. |
useHeaderFooterState() | Active header or footer scope, or null |
useNoteScopeState() | Active footnote or endnote scope |
useNotePropertiesState() | Note numbering properties, or null |
useContextMenuTarget() | HTMLElement | null that received the last context-menu action |
useTableBorderTargetLabel() | string label of the table-border target |
useToolbarContext() | { t, onSave } |
useToolbarLabel() | (key) => string for the active scope |
useToolbarLabelFor(t) | (key) => string for a given translator |
useScopeClassName() | '', 'docx-editor ', or 'docx-editor dark ' |
useScopedChromeAnchor(findAnchor, placement) | { ref, style } for overlay chrome |
Dialog draft contexts
Call these hooks in a child of the matching dialog. Each returns values, setValue(name, value), errors, isEnabled, apply(), and cancel():
| Hook | Draft type | Draft keys |
|---|---|---|
usePageSetupDialog() | PageSetupDialogFields | pageWidth, pageHeight, orientation, marginTop, marginBottom, marginLeft, marginRight, scope |
useParagraphDialog() | UseParagraphDialogReturn['values'] | direction, alignment, indentLeft, indentRight, special, specialBy, spaceBefore, spaceAfter, lineRule, lineValue, contextualSpacing, keepNext, keepLines, widowControl, pageBreakBefore, tabStops, clearedAllTabStops |
useTextFormFieldDialog() | TextFormFieldDialogFields | defaultText, type, maxLength, format, enabled |
useRevisionMarkupDialog() | ResolvedRevisionMarkup | insertions, deletions, changedLines, movedFrom, movedTo, cells, formatting, trackMoves, trackFormatting |
useCompareDocumentsDialog() | CompareDocumentsDialogValues | revised, authorName, detectFormatChanges, removeDateAndTime |
useWatermarkDialog() | WatermarkDialogFields | kind, imageData, imageName, scale, washout, text, fontFamily, fontSizePt, bold, italic, transparency, color, layout |
useBookmarkDialog() | BookmarkDialogFields | name, sortBy, showHidden |
useSplitCellDialog() | SplitCellDialogFields | columns, rows (strings) |
Most custom Field names match draft keys, with two exceptions:
| Dialog | Field name | Draft keys |
|---|---|---|
| Page Setup | pageSize | Updates pageWidth and pageHeight. Neither is a Field name. |
| Paragraph | None | clearedAllTabStops is draft state only. |
| Hook | Extra member |
|---|---|
useParagraphDialog() | mixed |
useRevisionMarkupDialog() | reset() restores default draft values. Call apply() to apply them. |
useSplitCellDialog() | limits: the selected cell's maximum row and column counts, or null |
Page sizes, margins, and indents use twips (1,440 per inch). Paragraph spacing uses points, except lineValue with lineRule: 'multiple', which is a line multiple. For the compare dialog's progress and cancel state, see Compare documents.
For a custom input, see Connect a custom input.
Apply a value
useEditorValueCommand reads the selected value and applies a new one. This slider sets the font size:
import { useState } from 'react';
import { useEditorValueCommand, useHistoryGroup } from '@docx-editor.dev/react';
export function FontSizeSlider() {
const size = useEditorValueCommand('font.size');
const gesture = useHistoryGroup({ kind: 'range' });
const [error, setError] = useState<string | null>(null);
return (
<label title={size.disabledReason ?? undefined}>
{size.value === null ? 'Mixed' : `${size.value / 2} pt`}
<input
ref={gesture.ref}
type="range"
min={16}
max={144}
value={size.value ?? 22}
disabled={!size.isEnabled}
onChange={(event) => {
const result = size.execute(Number(event.currentTarget.value), gesture.options());
setError(result.ok ? null : result.reason);
}}
/>
<output aria-live="polite">{error}</output>
</label>
);
}Font size uses half-points: 24 means 12 pt. null means mixed or unavailable, and value follows undo and redo. Other value slots include text.color (six hex digits without #), font.family, text.highlight, styles.style, list.lineSpacing, and image slots.
execute(value, options?) returns ExecResult:
| Field | Meaning |
|---|---|
ok | false when the editor refused the command. |
code, reason | Present when ok is false. Use code in logic and show reason. |
changed | true when the document changed. Formatting at a caret can change only the typing format. |
history?.kind | started, extended, split, or none for grouped writes. split does not mean failure. |
useEditorCommand().execute(options) takes the same options but returns a boolean. For the full result of a raw command, call editor.exec(command, options).
Group an input gesture
useHistoryGroup({ kind }): { ref, options } makes a continuous gesture one undo step. Attach ref to the control, and pass options() to each command from that gesture:
kind | Use it for | What ends the gesture |
|---|---|---|
range, repeat | A slider, or a button that repeats while held | Pointer release or cancel, lost capture, keyup, or blur |
keyboard | Repeated keydown events | Keyup, Escape, or blur |
native-color | An HTML color input | Native change, next activation, Escape, or blur |
The binding disposes the group on unmount or editor replacement. For repeat, your app owns the timer. A native color change does not guarantee that the picker closed.
useEditorEvent('historyDiagnostic', handler) reports interrupted groups. To group a review decision, see Group a review decision.
Limits
- Call hooks inside
DocxEditor.Root. Outside it,useDocxEditor()returnsnull. canUndoreports undo availability, not unsaved changes. Track save completion yourself.selectionChangealso fires for state unrelated to the selection.- Dialog draft hooks read a draft; they do not open the dialog.
- Image value commands reject the
historyGroupoption. useContentControlWidget().showMonth(year, month)accepts integer years 100 through 9999 and zero-based months 0 through 11.