Custom controls
Commands, queries, presets, and plugins for a toolbar of your own.
DocxEditor can keep its editing surface while a host application replaces the built-in toolbar
and context menus.
Hide the built-in controls
Disable either surface through mode:
<DocxEditor
document={file}
mode={{ kind: "edit", toolbar: false, contextMenus: false }}
/>Commands and queries
Text and paragraph commands come from @portone/docx-editor/commands. Operations on an existing
table come from @portone/docx-editor/table, while insertTable comes from
@portone/docx-editor/commands.
Commands follow the ProseMirror command contract. Calling one with an EditorState reports whether
it applies, while passing a dispatch function performs the edit.
import type {
Command,
EditorState,
EditorView,
} from "@portone/docx-editor";
import { isBoldActive, toggleBold, undo } from "@portone/docx-editor/commands";
function Toolbar({ view, state }: { view: EditorView; state: EditorState }) {
const run = (command: Command) => {
command(state, (transaction) => view.dispatch(transaction), view);
view.focus();
};
return (
<div>
<button
type="button"
aria-pressed={isBoldActive(state)}
onClick={() => run(toggleBold)}
>
Bold
</button>
<button type="button" disabled={!undo(state)} onClick={() => run(undo)}>
Undo
</button>
</div>
);
}onReady provides the editor view, and onChange fires whenever the editor state changes. Use
both to keep custom controls in sync with the current selection.
The query prefixes describe what they return:
is*Activereports a boolean toggle.active*reports the current value or a mixed selection.can*reports whether the related action applies.isIn*reports containment.
DEFAULT_X names the built-in default for the prop or field named x. Selection-level readers use
selection*, while document-level readers use document*.
Package commands account for locked content before reporting that they apply. Wrap third-party or
application commands with canRunCommand(command, state) when they also need to respect document
locks.
Use insertTab to insert the same editable DOCX tab as the built-in Tab binding. It applies inside
an ordinary paragraph; table cells and list paragraphs keep their navigation and level-changing Tab
behavior.
Tables
Table-cell controls use canSetCellFormatting, activeCellVerticalAlign, setCellVerticalAlign,
activeCellPadding, and setCellPadding. The gate query covers the whole selection, alignment
reports top, center, bottom, or mixed, and each padding side independently reports a point
value, mixed, or null. A padding command writes only the sides supplied. activeCellBorderColor,
canSetCellBorderColor, and setCellBorderColor operate on effective visible borders; editing a
border inherited from its table or table style creates a direct override only on the visible sides
of the selected cells.
Images, links, and annotations
Image controls can use IMAGE_FILE_ACCEPT, imageFilesIn, and insertImageFiles. Lower-level
controls can combine readImageFile, fittedExtent, and insertImage.
Link controls use canSetLink, activeLink, activeLinkSpan, setLink, and removeLink.
External links are editable; bookmark links remain preserved.
Comment controls use canAddComment, addComment, documentComments, selectComment,
updateComment, removeComment, addCommentReply, updateCommentReply, removeCommentReply,
and setCommentResolved. commentAuthor sets the identity written by the built-in comment and
reply composers; when omitted, the built-in UI writes Anonymous.
documentNotes returns the distinct footnotes and endnotes referenced by the main document,
including their display labels and plain-text bodies.
Document zoom
The editor fits the page to its available width by default. Its toolbar offers Fit, 50%, 75%, 100%, 125%, and 150%. Fixed zoom remains selected on a narrow screen and makes the document horizontally scrollable instead of forcing it back to fit.
Use defaultZoom when the editor should own the selection, or zoom with onZoomChange when the
host should own it:
import { DocxEditor, type DocxEditorZoom } from "@portone/docx-editor";
import { useState } from "react";
function Preview({ file }: { file: File }) {
const [zoom, setZoom] = useState<DocxEditorZoom>("fit-width");
return <DocxEditor document={file} zoom={zoom} onZoomChange={setZoom} />;
}Numeric values from 0.25 through 2 are accepted. The root entry exports DEFAULT_ZOOM_LEVELS
for a custom toolbar. A read-only editor has no built-in toolbar, but a host can still set zoom.
Zoom changes only the browser presentation; it does not change pagination measurements, the
document model, or exported OOXML. Persisting a reader's choice belongs to the host application.
The comment panel reads the same factor from the --docx-editor-zoom custom property, set on the
editor workspace, so comment text scales with the paper instead of standing apart from it. Its font
sizes stop shrinking at a readable floor, and the panel keeps its width at every zoom level.
Locked content
A lock is an OOXML content control that shuts its contents, so it is part of the document rather
than editor state: it survives a round trip and travels with the file.
A lock the open document already carries is honored in every mode, read-only included, and no prop
turns it off.
What mode: { kind: "edit", locking: true } adds is the authoring side, the built-in menu entries
that put a lock on and take one off, since settling part of a document is a template author's act
rather than something every reader of a form should be handed.
Custom controls read the selection with selectionLock, which answers with one of four states
instead of a pair of booleans a menu has to combine:
import {
lockSelection,
selectionLock,
unlockSelection,
} from "@portone/docx-editor/commands";
const lock = selectionLock(state); // "none" | "lockable" | "locked" | "mixed"lockable means there is text to shut, locked that the selection reaches a lock to lift, and
mixed that it does both.
lockSelection and unlockSelection are ordinary commands, so each reports whether it applies
before it is offered.
Locking wraps one control per paragraph the selection runs through, and only a text selection has anything to lock: a selected image or a block of table cells is not a stretch of text a control can hold. Reaching the edge of a control counts as reaching it, so a caret resting against either end of a locked field, or anywhere inside a locked cell, is enough to unlock it. A lock is lifted whole, and unlocking keeps the control itself, removing only its lock.
selectionTouchesLocked(state) answers whether the selection meets locked content at all, which is
what a control outside this package asks before editing.
documentHasLocked(doc) takes the document rather than the state and reports whether the file
carries any lock, which is how a screen decides to show its locking affordances at all.
Presets and plugins
The root entry exports the preset values used by the built-in controls, including DEFAULT_COLORS,
DEFAULT_FONT_SIZES, DEFAULT_LINE_SPACINGS, DEFAULT_CELL_BORDERS, and DEFAULT_FONTS. Pass
replacement lists through the presets prop when the built-in controls should offer different
values. Color presets remain quick choices; the built-in color controls also accept any three- or
six-digit HEX color.
Pass ProseMirror plugins through the plugins prop. Consumer keymaps receive keyboard events
before the built-in keymap, so an application can override bindings such as Mod-k without
replacing the editor.
See Features for behavioral limits and the published TypeScript declarations for complete signatures.