# DOCX Editor: full documentation
This file contains the complete docs for the @docx-editor.dev/* packages, concatenated and stripped of HTML/frontmatter so an LLM can ingest it in one shot.
Source of truth: https://www.docx-editor.dev/docs/2.x
Generated from: src/content/docs/*.mdx (49 files)
---
# Build a DOCX agent
Source: https://www.docx-editor.dev/docs/2.x/build-a-docx-agent
This page connects a tool-calling model to a DOCX file. You expose a small
catalog of document tools. Each tool validates its input and calls
[`@docx-editor.dev/editor-api`](/docs/2.x/editor-api). The model does not
generate Office Open XML (OOXML).
## Try the writer agent
Open the [writer agent demo](/#writer-agent) on the homepage. Choose
**Draft a mutual NDA.** After the draft finishes, choose **Redline for
clarity**.
The demo uses the Vercel AI SDK. The document tools work with any tool-calling
framework.
For an agent that continues working after the browser closes, run the
[server agent review example](https://github.com/eigenpal/docx-editor/tree/main/examples/server-agent-review).
Its worker joins a shared Hocuspocus room and publishes suggestions for browser
peers to accept or reject. Scripted review works without a model API key.
## Before you start
- You need `@docx-editor.dev/editor-api` and `@docx-editor.dev/core`.
- If you show the document in a page, install `@docx-editor.dev/react` or
`@docx-editor.dev/vue`.
- For comments or tracked changes, pass a non-empty `author`. Browser review
also requires `@docx-editor.dev/pro` and its review module. The server runtime
provides these writes without a browser review module.
{error.detail ?? error.code}
; } if (pending || !document) { returnConnecting…
; } return{control.controlType}
{{ control.controlType }}
The document failed to load.
Loading…
# scaffolds packages/i18n/.json with nulls
# fill in the strings, then
bun run i18n:status # shows remaining nulls per locale
bun run i18n:validate # CI runs this; missing keys fail
```
Use a BCP 47 code (`pl`, `pt-BR`, `zh-CN`). Open a PR with the filled JSON.
Partial translations are valid: anything left `null` renders in English, so a
locale can ship incomplete and improve over time.
## Fixing an existing locale
Edit the string in `packages/i18n/.json`, run `bun run i18n:validate`,
and open a PR. No code changes needed.
## Next steps
- [i18n package](/docs/2.x/i18n): wiring locales into the editor
- [i18n API reference](/docs/2.x/api/i18n): every locale export
- [Open a PR](https://github.com/eigenpal/docx-editor/pulls)
---
# 2.x/i18n/index
Source: https://www.docx-editor.dev/docs/2.x/i18n/index
Locale data for the editor UI. Both adapters consume it through the `i18n` prop.
```bash
npm install @docx-editor.dev/i18n
```
Add this package as a direct dependency when importing its catalogs, including for the editor's `i18n` prop.
## UI language and date input
`i18n` supplies UI translations; `locale` selects regional date input and generated
document labels. Neither setting infers the other. UI strings default to English
or an inherited catalog; `locale` defaults to `en-US`.
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import { pl } from '@docx-editor.dev/i18n';
// English UI, Polish dates (with no inherited catalog)
;
// Polish UI, Polish dates
;
```
In Vue, use `locale="pl-PL"` and `:i18n="pl"` on ``.
For composed editors, wrap Root and its chrome in `LocaleProvider`; Root has no
`i18n` prop. See the [React](/docs/2.x/react/composition#ui-language-and-date-input)
and [Vue](/docs/2.x/vue/composition#ui-language-and-date-input) examples.
See [form fields](/docs/2.x/guides/fields) for supported date input.
## Available locales
| Code | Language | Named export |
| ------- | -------------------- | ------------ |
| `en` | English | `en` |
| `pl` | Polish | `pl` |
| `de` | German | `de` |
| `fr` | French | `fr` |
| `pt-BR` | Brazilian Portuguese | `ptBR` |
| `he` | Hebrew | `he` |
| `hi` | Hindi | `hi` |
| `id` | Indonesian | `id` |
| `tr` | Turkish | `tr` |
| `zh-CN` | Chinese (Simplified) | `zhCN` |
Two import shapes are supported:
- **Named exports off the root** (`import { pl } from '@docx-editor.dev/i18n'`). Use this when you ship a small static list of locales. Hyphenated codes (`pt-BR`, `zh-CN`) become camelCase exports (`ptBR`, `zhCN`).
- **Per-locale subpath imports** (`import pl from '@docx-editor.dev/i18n/pl'`). Use this when you dynamically load locales; the per-locale subpath code-splits so users only download the language they need.
Verify what your installed version ships:
```bash
ls node_modules/@docx-editor.dev/i18n/
```
## Wiring into the editor
Pass the catalog to `i18n`. These examples change UI language only; date input
keeps the default `en-US` conventions.
#### React
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import { pl } from '@docx-editor.dev/i18n';
;
```
#### Vue
```vue
```
For several editors, or for chrome parts you compose yourself, put the catalog in context once instead:
#### React
```tsx
import { DocxEditor, LocaleProvider } from '@docx-editor.dev/react';
;
```
#### Vue
```vue
```
In both cases, the catalog merges over English, so a locale that has not translated every key falls back per key rather than per language. Providers nest: an inner one, or an `i18n` prop under an outer provider, overrides only the keys it names. Chrome you write from scratch reads the same catalog through `useTranslation()`:
#### React
```tsx
import { useTranslation } from '@docx-editor.dev/react';
const { t } = useTranslation();
;
```
#### Vue
```vue
```
## Next steps
- [Contributing a translation](/docs/2.x/i18n/contributing): add or improve a locale with one JSON file
- [i18n API reference](/docs/2.x/api/i18n)
- [React API reference](/docs/2.x/api/react) and [Vue API reference](/docs/2.x/api/vue)
---
# 2.x/index
Source: https://www.docx-editor.dev/docs/2.x/index
`docx-editor` is a what-you-see-is-what-you-get (WYSIWYG) DOCX editor for React
and Vue 3.
It parses Office Open XML (OOXML), renders paginated pages, and saves the edited
state as a DOCX file. Browser editing needs no upload or conversion service.
Install an adapter, load a DOCX file, and save the result.
Build an interface with public components and hooks.
Check feature support, round-trip behavior, security, and limits.
Define tools a model can call to read, draft, and redline a DOCX.
## Packages
| Package | Purpose | License |
| ----------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------- |
| [`@docx-editor.dev/react`](/docs/2.x/react) | React adapter and composition hooks | Apache 2.0 |
| [`@docx-editor.dev/vue`](/docs/2.x/vue) | Vue 3 adapter and composables | Apache 2.0 |
| [`@docx-editor.dev/core`](/docs/2.x/core) | Framework-independent OOXML, document, layout, paint, and editor engine | Apache 2.0 |
| [`@docx-editor.dev/editor-api`](/docs/2.x/editor-api) | Browser and server automation through a documented Office.js-compatible subset | EigenPal Pro License |
| [`@docx-editor.dev/pro`](/docs/2.x/pro) | Tracked changes, comments, and custom nodes | EigenPal Pro License |
| [`@docx-editor.dev/i18n`](/docs/2.x/i18n) | Shared interface translations | Apache 2.0 |
| [`@docx-editor.dev/fonts`](/docs/2.x/guides/fonts) | Open-licensed substitutes for common Word fonts | Apache-2.0 AND OFL-1.1 AND LicenseRef-GUST-Font-License |
The Pro packages are licensed under the EigenPal Pro License, and you can compare and buy license and support levels on the [pricing page](https://www.docx-editor.dev/pricing).
## Select packages
| Goal | Packages |
| ---------------------------------------------------------------------- | -------------------------------------------------------------- |
| React editor | `@docx-editor.dev/react` and `@docx-editor.dev/core` |
| Vue editor | `@docx-editor.dev/vue` and `@docx-editor.dev/core` |
| Tracked changes, comments, or custom nodes | Add `@docx-editor.dev/pro` |
| Server-side editing through the documented Office.js-compatible subset | `@docx-editor.dev/editor-api` and `@docx-editor.dev/core` |
| Browser automation through the same subset | Add `@docx-editor.dev/editor-api` and use its `/browser` entry |
Install the React editor:
```bash
npm install @docx-editor.dev/react @docx-editor.dev/core
```
For Vue, replace `@docx-editor.dev/react` with `@docx-editor.dev/vue`.
## Document capabilities
| Area | Examples |
| ----------- | ----------------------------------------------------------------------- |
| Text | Formatting, fonts, theme colors, paragraph styles, and character styles |
| Structure | Tables, lists, numbering, footnotes, endnotes, and content controls |
| Page layout | Sections, columns, headers, footers, margins, and pagination |
| Content | Images, hyperlinks, bookmarks, and fields |
| Review | Tracked changes and comments with `@docx-editor.dev/pro` |
Support differs by feature and operation. See
[Word fidelity](/docs/2.x/word-fidelity) for editing, rendering, and round-trip
status. Use the [live demo](https://docx-editor.dev/editor) to test a document.
## Round-trip behavior
Saving preserves untouched package content and unsupported OOXML.
| Content | Behavior |
| -------------------------------------- | ----------------------------------------- |
| Modeled document structures | Uses typed nodes for layout and editing. |
| Unsupported or misplaced XML | Keeps generic nodes in the document tree. |
| Media, fonts, macros, and VBA projects | Copies package payloads through save. |
| Custom XML and add-in markup | Preserves structural content. |
For the preservation contract and test method, see
[Word fidelity](/docs/2.x/word-fidelity). For engine details, see
[Architecture](/docs/2.x/core/architecture).
## Next steps
- [Quickstart](/docs/2.x/quickstart): Load, edit, and save a DOCX file.
- [Build a DOCX agent](/docs/2.x/build-a-docx-agent): Define document tools for a model.
- [Installation](/docs/2.x/installation): Configure supported frameworks.
- [React composition](/docs/2.x/react/composition): Build a React interface.
- [Vue composition](/docs/2.x/vue/composition): Build a Vue interface.
---
# Installation
Source: https://www.docx-editor.dev/docs/2.x/installation
Use one of these supported framework versions:
- React `^18 || ^19`
- Vue `^3.3`
## Node version
To run the engine outside a browser, use Node `^20.16.0 || >=22.3.0`.
Anything that measures text needs the text shaper. The shaper reaches Node
builtins through `process.getBuiltinModule`, which arrived in Node 20.16.0 and
22.3.0. On an earlier version the shaper does not start. The engine then reports
the Node version as the cause, rather than a missing binary.
This applies to server-side rendering, headless automation with
`@docx-editor.dev/editor-api`, and build-time rendering. A browser-only app is
unaffected.
The floor matters more than it looks. Measurement decides where lines wrap and
pages break, so a shaper that cannot start is not a degraded mode. It changes
your page count.
## Pick a package
Each adapter declares the engine as a peer dependency. Install the engine and one adapter.
Use these commands to install the packages that your app needs:
#### React
```bash
npm install @docx-editor.dev/react @docx-editor.dev/core
```
#### Vue
```bash
npm install @docx-editor.dev/vue @docx-editor.dev/core
```
The other packages are the same for both adapters:
```bash
# Tracked changes, comments, custom nodes
# EigenPal Pro License: https://www.docx-editor.dev/pricing
npm install @docx-editor.dev/pro
# Office.js-compatible editing API, on a server or with an active editor instance
# EigenPal Pro License: https://www.docx-editor.dev/pricing
npm install @docx-editor.dev/editor-api @docx-editor.dev/core
# Open-licensed substitutes for common Word fonts (optional)
npm install @docx-editor.dev/fonts
```
## Mount the editor
Import the component and the stylesheet once.
This example creates an empty document:
#### React
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import '@docx-editor.dev/core/styles/editor.css';
export default function App() {
return (
);
}
```
#### Vue
```vue
```
The Vue stylesheet imports the core stylesheet that React uses.
Your app does not need Tailwind or an icon font.
`document` accepts an `ArrayBuffer`, `Uint8Array`, `DocumentHandle`, or `'blank'`.
Use `'blank'` for an empty document.
If you omit `document`, the editor stays idle until you pass document bytes.
Meet these layout requirements in both adapters:
- Import the stylesheet to style the editor controls.
- You do not need Tailwind because the precompiled CSS uses the `.docx-editor` scope.
- Give the parent element a height because `` fills its parent.
- A parent without a height collapses, so the editor does not appear.
## Use server-rendered frameworks
The editor uses the Document Object Model (DOM) to measure text when it mounts.
You must render the editor on the client.
Vite apps need no extra boundary.
Server-side rendering (SSR) frameworks need a client-only boundary.
Use the guide for your framework:
## Fonts
The editor uses font bytes to measure line wraps and page breaks.
It loads embedded document fonts without configuration.
Some documents reference Word default fonts without embedding them.
For these documents, `@docx-editor.dev/fonts` supplies open-licensed substitutes.
Five families match advance widths. The Century Gothic substitute stays within
1% in the package fidelity check.
`packagedFonts()` supplies them per document: the editor calls it after parsing
with the families that file declares. It loads a family when the document names
it, or when that family is the document's default face, so a document pays for
what it declares instead of all 20 eager faces. No request leaves your origin.
The default face counts because a run that names no font still has to be measured
in one. That face is Calibri, so Carlito loads for every document.
#### React
```tsx
import { DocxEditor, useFonts } from '@docx-editor.dev/react';
import { packagedFonts } from '@docx-editor.dev/fonts';
function Editor({ bytes }: { bytes: Uint8Array }) {
const fonts = useFonts(packagedFonts());
return ;
}
```
#### Vue
```vue
```
`packagedFonts()` resolves after the document is parsed, so the first layout uses
fixed measurement and the editor re-paginates when the faces arrive. Edits made in
between survive that; the undo history behind them does not. For a document that
must paginate correctly on the first pass, use `defaultFonts()` instead. For more
information, see [Fonts and measurement](/docs/2.x/guides/fonts#choose-between-lazy-and-eager-loading).
## Edit documents without a browser
Use [`@docx-editor.dev/editor-api`](/docs/2.x/editor-api) to edit a `.docx` without a browser.
You can use it on a server, in a worker, or in a script.
The package opens document bytes and uses an Office.js-compatible batching object model.
It saves your changes as document bytes.
## Next steps
- Follow the [quickstart](/docs/2.x/quickstart) to load, edit, and save a `.docx`.
- Read [React composition](/docs/2.x/react/composition) to build custom editor controls.
- Read [Vue composition](/docs/2.x/vue/composition) to compose the editor with Vue components.
- Review [Word fidelity](/docs/2.x/word-fidelity) for feature support and round-trip behavior.
- Review [React props](/docs/2.x/react/props) and the [React API reference](/docs/2.x/api/react).
---
# DOCX collaboration reference
Source: https://www.docx-editor.dev/docs/2.x/pro/collaboration
This reference documents the complete configuration and API behavior for
real-time DOCX collaboration with the Pro package. It covers providers, room
lifecycles, presence, recovery, and resource limits.
For a basic WebRTC setup, use the
[real-time collaboration quickstart](/docs/2.x/collaboration).
The Pro module replicates text, structure, formatting, tables, headers, footers,
notes, drawings, relationships, and embedded parts. Peers also share presence
and remote selections across paragraphs.
Collaboration requires the collaboration module from
[`@docx-editor.dev/pro`](/docs/2.x/pro).
Without that module, the editor does not attach a replica. Local undo remains
active. `snapshot().collaborationStatus` is `'inactive'`.
```mermaid
flowchart LR
accTitle: Collaboration between two document editors
accDescr: Each editor updates a local document and Yjs document. A provider synchronizes both Yjs documents.
subgraph A[Peer A]
EA[Editor] <--> DA[Local document]
DA <--> YA[Yjs document]
end
YA <--> T[WebRTC, Hocuspocus, or custom provider]
T <--> YB[Yjs document]
subgraph B[Peer B]
YB <--> DB[Local document]
DB <--> EB[Editor]
end
```
## Prerequisites
Install the Pro package, Yjs, and the provider for your transport.
For WebRTC, run:
```bash
npm install @docx-editor.dev/pro yjs y-webrtc
```
For Hocuspocus, run:
```bash
npm install @docx-editor.dev/pro yjs @hocuspocus/provider
```
`yjs`, `y-webrtc`, and `@hocuspocus/provider` are optional Pro peer
dependencies. Pro installs `y-protocols`.
## Connect with `useWebrtcCollaboration`
`useWebrtcCollaboration` owns the WebRTC room. It creates the replica and
builds `modules`. It destroys the room when you leave.
The WebRTC subpath keeps the network provider out of review-only bundles.
Import `@docx-editor.dev/pro/react/webrtc` or
`@docx-editor.dev/pro/vue/webrtc`.
Pass `room` to connect when the component mounts. Call `leave` to end the
session. Do not mount the editor while `pending` is true.
The hook returns an object. Its `session` field contains a
`CollaborationSession`. The session exposes identity, status, presence, and
undo. It does not expose `attach` or `gateOperations`.
`CollaborationSession`, `CollaborationStatus`, and `CollaborationFailure` are
exported types from `@docx-editor.dev/pro/react` and
`@docx-editor.dev/pro/vue`.
Room failures make `connect` and `rejoin` resolve to a
`CollaborationFailure`. Success resolves to `null`. `rejoin` still throws if
you call it before a connection attempt. The hook also puts room failures in
`error` for rendering:
```tsx
const failure = await connect(options);
if (failure?.code === 'initialization-timeout') {
// No room server answered.
}
```
Branch on `failure.code` or `error.code` instead of matching message strings.
`error` reports an initial connection failure or a session failure after
connection. An expired token, `concurrent-seed`, or digest mismatch can occur
after a successful join. Always handle `error`, even when `document` is not
`null`.
The `` host accepts `document` and `modules`. You do not need
`DocxEditor.Root` for collaboration.
#### React
```tsx
import { useRef } from 'react';
import { DocxEditor, type DocxEditorRef } from '@docx-editor.dev/react';
import { useWebrtcCollaboration } from '@docx-editor.dev/pro/react/webrtc';
type CollaborativeEditorProps = {
roomId: string;
bytes: Uint8Array;
actorId: string;
name: string;
};
export function CollaborativeEditor({ roomId, bytes, actorId, name }: CollaborativeEditorProps) {
const editorRef = useRef(null);
const { document, modules, session, pending, error, leave } = useWebrtcCollaboration({
room: {
roomId,
identity: { actorId, name },
bootstrap: { kind: 'create-or-join', document: bytes },
},
});
async function leaveRoom() {
// `leave` requires current bytes. The hook cannot read them.
const saved = await editorRef.current?.save();
if (saved) {
leave(new Uint8Array(saved));
}
}
if (error) return {error.detail ?? error.code}
;
if (pending || !document) return Connecting…
;
return (
<>
>
);
}
```
#### Vue
```vue
{{ error.detail ?? error.code }}
Connecting…
```
With `create-or-join`, every peer uses the same room and bootstrap settings.
Each peer must use a unique `actorId`. The first peer seeds the empty room from
`document`. Later peers join the existing room. Use `create` or `join` when each
host already knows its role.
Two isolated peers can seed the same room at the same time. This creates a room
that clients cannot repair. Each replica reports `concurrent-seed` and stops.
Create a new room from saved bytes.
Omit `room` when the user chooses to share or join later. Then call `connect`
with the same options. `connect` and `leave` keep stable identities across
renders.
The editor reads `modules` at construction time. Set `key` to the session
`sessionId`, as shown in the examples. Without `key`, a connection after mount
does not register the collaboration module. The editor continues local editing,
and no changes replicate. The session logs a warning if you create it without
attaching it.
The hook also returns `ydoc` and `provider`. Both values are `null` while the
connection is pending. Use them for persistence or direct provider access.
`rejoin(bytes)` leaves with saved bytes and rejoins the same room after an
`error` status.
The default Pro entry exports `collaborationModule` without a network provider.
Import `@docx-editor.dev/pro/collaboration` for Yjs factories. Import
`@docx-editor.dev/pro/collaboration/webrtc` for room helpers such as
`createCollaborationRoomId`.
## Mount the editor
A collaborative root needs three values from the room. `key` remounts the
editor for a new session. `document` contains the room bytes. `modules` contains
the hook modules. Incorrect values can create a working editor that does not
replicate edits.
`DocxEditorCollaborationRoot` sets all three values. Import it with
`DocxEditorCollaboration` from the framework entry. It also uses the room
identity name as `author`. Set `author` to override this value. Other
`DocxEditor.Root` props pass through.
#### React
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import { DocxEditorCollaboration, DocxEditorCollaborationRoot } from '@docx-editor.dev/pro/react';
import {
useHocuspocusCollaboration,
type UseHocuspocusCollaborationConnectOptions,
} from '@docx-editor.dev/pro/react/hocuspocus';
interface CollaborativeEditorProps {
room: UseHocuspocusCollaborationConnectOptions;
}
export function CollaborativeEditor({ room }: CollaborativeEditorProps) {
const collaboration = useHocuspocusCollaboration({ room });
if (collaboration.error) {
return {collaboration.error.detail ?? collaboration.error.code}
;
}
return (
Connecting…}>
);
}
```
React renders the `fallback` prop whenever `document` is `null`. The root
cannot distinguish a pending connection from a failure. Handle `error` before
you render the root.
#### Vue
```vue
{{ collaboration.error.detail ?? collaboration.error.code }}
Connecting…
```
Vue composables return refs. Wrap the return with `reactive` before you pass it
to `DocxEditorCollaborationRoot`. Vue renders the named `fallback` slot whenever
`document` is `null`. Without that slot, it renders nothing. Handle `error`
before you render the root.
Use `DocxEditor.Root` directly when one page mounts two rooms, or when the bytes come from
somewhere the component cannot see:
```tsx
if (collaboration.error) {
return {collaboration.error.detail ?? collaboration.error.code}
;
}
if (!collaboration.document) {
return Connecting…
;
}
return (
);
```
## Show status and participants
`useCollaborationStatus()` returns these fields:
- `status` gives the session state, or `inactive` when no session exists.
- `reason` gives the failure for the present state. It clears after recovery.
- `lastFailure` keeps the latest terminal error after the status changes.
- `live` is true when edits made now reach the room.
- `diverged` is true when `status` is `error` or `destroyed`. Call `rejoin` to
recover a room that the hook connected before.
- `attached` is true when an editor has attached its document port. A live,
unattached session usually means the editor did not remount for the session.
`useCollaborationParticipants()` returns the room participants.
Both hooks accept an optional `session`. Omit it to read the session from the
editor provider. Pass it for a room that the editor does not own. The presence
parts use the same rule. `session` is optional on
`DocxEditorCollaboration.Avatars` and
`DocxEditorCollaboration.CaretLabels`.
All of these are available from `@docx-editor.dev/pro/react` and
`@docx-editor.dev/pro/vue`.
If you own the Yjs resources, use `useDocumentCollaboration` from the same
entries. It provides the same `connect` and `leave` lifecycle without WebRTC.
## Show avatars and caret labels
`DocxEditorCollaboration` provides presence controls. Import it from
`@docx-editor.dev/pro/react` or `@docx-editor.dev/pro/vue`. Mount its parts
anywhere in the editor provider tree.
`DocxEditorCollaboration.Avatars` renders participant initials. It puts the
local participant first.
```tsx
```
Avatar colors match each collaborator's tracked changes and comments. `max`
collapses extra avatars into a `+N` chip. Use
`DocxEditorCollaboration.Avatar` to render one participant.
You can replace each avatar disc with a renderer. It receives `participant`,
`color`, `initials`, and the locally resolved optional `avatarUrl`.
```tsx
{({ participant, avatarUrl, initials }) =>
avatarUrl ? (
) : (
{initials}
)
}
```
`participant` contains `actorId`, `name`, optional `color`, optional `role`, and
`isLocal`.
Avatars show the picture declared for a collaborator, so one declaration covers
every surface that draws that person:
```tsx
```
The review card, the caret label, and the avatar stack all resolve it by display
name, which is the string `w:author` carries in the saved file. A declared color
outranks the one a peer publishes in `identity.color`: the declaration is your
own record of who someone is, and a peer must not be able to make their caret
disagree with their comment cards. `identity.color` still applies to anyone you
have not declared.
`DocxEditorCollaboration.CaretLabels` replaces each remote caret label. The
engine positions and colors the label. Your renderer mounts in the adapter
tree, so editor and review hooks work inside it.
#### React
```tsx
{({ selection, participant, color, avatarUrl }) => (
)}
```
#### Vue
```vue
```
The renderer receives `selection`, optional `participant`, `color`, and optional
`avatarUrl`. In Vue, it is the default scoped slot. Without a renderer, the
label shows the collaborator's name.
The engine marks the label layer `aria-hidden` and disables pointer events.
Screen readers do not announce label content. Label content cannot receive
clicks or focus. Put interactive or announced presence controls in your own
chrome.
For CSS styling, use the `docx-remote-caret-label` class,
`--doc-remote-color` custom property, and `data-docx-remote-actor` attribute.
## Recover a diverged replica
A status of `error` is terminal. The replica refused an update and kept its
copy. The session now refuses edits, and other peers might not have its latest
changes. Waiting does not fix the replica. Call `rejoin`:
```tsx
const { rejoin } = useHocuspocusCollaboration({ room });
async function rejoinRoom() {
const saved = await editorRef.current?.save();
if (saved) {
await rejoin(new Uint8Array(saved));
}
}
```
Save the editor bytes before you call `rejoin`. It leaves with those bytes, then
joins the same room with `{ kind: 'join' }`. A successful join uses the room
copy. It can drop unreplicated changes that existed when the session failed.
After a failed join, the saved bytes stay mounted locally.
## Edit offline
Set `offlineEditing: true` in the `room` or `connect` options. The session then
accepts edits while its status is `disconnected`. It merges buffered updates
after reconnection. Show the status so users know when edits have not reached
the room. The `error` status remains terminal. Every room factory and hook
accepts this option.
## Use your own signaling
`DEMO_SIGNALING_ENDPOINTS` is a public demo signaling service. Do not use it
for production.
For production, pass your signaling URLs to `connect` or `room`. Also provide
your own Traversal Using Relays around NAT (TURN) servers. Many networks block
direct WebRTC connections without TURN.
## Connect to a Hocuspocus server
`useHocuspocusCollaboration` owns a room on a
[Hocuspocus](https://tiptap.dev/docs/hocuspocus) server. It has the same
options, return values, and lifecycle as `useWebrtcCollaboration`. A
server-backed room does not need signaling or TURN configuration.
Import the hook from `@docx-editor.dev/pro/react/hocuspocus`. Import the Vue
composable from `@docx-editor.dev/pro/vue/hocuspocus`.
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import { useHocuspocusCollaboration } from '@docx-editor.dev/pro/react/hocuspocus';
export function ServerBackedEditor({
roomId,
token,
bytes,
actorId,
name,
}: {
roomId: string;
token: string;
bytes: Uint8Array;
actorId: string;
name: string;
}) {
const { document, modules, session, pending, error } = useHocuspocusCollaboration({
room: {
url: 'wss://collab.example.test',
roomId,
token,
identity: { actorId, name },
bootstrap: { kind: 'create-or-join', document: bytes },
},
});
if (error) return {error.detail ?? error.code}
;
if (pending || !document) return Connecting…
;
return ;
}
```
`token` reaches the server's `onAuthenticate` hook. If tokens expire, pass a
callback instead of a string. The provider calls it for each reconnection.
A rejected token during the initial join produces `initialization-aborted`.
After a successful join, rejection sets the status to `error` and `error.code`
to `authentication-failed`. Handle that code by refreshing the credential and
calling `rejoin`. A `transport-disconnected` failure recovers on its own.
`syncedTimeoutMs` limits the initial sync wait. Its default is 30 seconds. A
timeout produces `initialization-timeout`.
The `createHocuspocusCollaboration` factory accepts the same options. Import it
from `@docx-editor.dev/pro/collaboration/hocuspocus`. It returns the room,
`ydoc`, and `provider`.
The Hocuspocus v4 server runs on Node, not Bun. Use
`@hocuspocus/provider` for the required authentication handshake.
## Read a room from a server
`readCollaborationDocument(ydoc)` returns the room's document as `.docx` bytes.
Use it for export, autosave to your own storage, search indexing, or rendering.
```ts
import { writeFile } from 'node:fs/promises';
import type * as Y from 'yjs';
import { readCollaborationDocument } from '@docx-editor.dev/pro/collaboration';
async function exportRoom(documentName: string, document: Y.Doc) {
await writeFile(`${documentName}.docx`, readCollaborationDocument(document));
}
```
Call `exportRoom` from Hocuspocus `onStoreDocument`, which receives the
synchronized `Y.Doc`.
It joins nothing. There is no identity, no `Awareness`, and no session, so the
job never appears in a room's participant list, and it creates no editing gate.
It does not write to the `Y.Doc`.
The `Y.Doc` must already hold the room's state. Connect your provider and wait
for its initial sync first. A document nobody seeded throws `not-initialized`
instead of returning a truncated file.
The function throws `CollaborationSchemaError` for `not-initialized`,
`concurrent-seed`, `blob-digest-mismatch`, materialization failures, and
resource-limit failures. Handle the error and keep the last valid export.
See the
[server-backed Hocuspocus example](https://github.com/eigenpal/docx-editor/tree/main/examples/collaboration-hocuspocus)
for persistence and DOCX export.
## The room is the document
While a room is live, the room holds the authoritative copy. An exported `.docx`
file is a snapshot of the room at one moment, not a branch of it.
A file edited outside the room cannot merge back in. Seeding the edited file
creates a new room with new identity, and no three-way merge exists between a
room and an external copy. This is the same rule the
[recovery flow](#recover-a-diverged-replica) applies within a room: when two
copies disagree, the room copy wins.
Keep one authority per document at a time:
- While people collaborate, treat the room as the document. Export snapshots for
backup, indexing, or review, and treat them as read-only.
- When collaboration ends, export the room and make the file the authority
again.
- To bring external edits into a live room, apply them as edits inside the room
— for example, paste the changed content — rather than re-seeding the file.
- To restart collaboration on an externally edited file, create a new room from
those bytes and retire the old room.
## Use the replication contracts
`@docx-editor.dev/core/collaboration/replication` holds the seam a replication
implementation binds to: the document port an adapter writes through, the
primitive journal it reads, and the descriptors that journal is made of.
```ts
import type {
CollaborationDocumentPort,
CanonicalPrimitiveJournal,
} from '@docx-editor.dev/core/collaboration/replication';
```
A host that renders presence and reads a status needs none of it, which is why
it is a separate subpath. Import `@docx-editor.dev/core/collaboration` for the
consumer types: identity, participants, remote selections, status, and failures.
## Integrate a custom Yjs provider
If you own a `Y.Doc` and awareness instance, call
`createDocumentCollaboration` from `@docx-editor.dev/pro/collaboration`. It
replicates the complete canonical package. You must destroy these resources.
Four rules make a bring-your-own-provider integration work:
1. Connect the provider before you use `bootstrap: { kind: 'join' }`. The
factory reads synchronized shared state. Without a connection, the join
fails with `initialization-timeout` after 30 seconds.
2. Pass an `Awareness` instance from `y-protocols/awareness`. It carries
presence and remote selections.
3. Send provider connection events to `session.setTransportStatus`. Without
this call, the status remains `ready` during an outage.
4. Some transports limit message size. The WebRTC wrapper provides message
framing. A WebSocket provider does not need it.
This example wires `y-websocket`:
```ts
import * as Y from 'yjs';
import { Awareness } from 'y-protocols/awareness';
import { WebsocketProvider } from 'y-websocket';
import { createDocumentCollaboration } from '@docx-editor.dev/pro/collaboration';
const ydoc = new Y.Doc();
const awareness = new Awareness(ydoc);
const provider = new WebsocketProvider('wss://example.test', 'room-1', ydoc, { awareness });
declare const currentUser: { id: string; name: string };
const identity = { actorId: currentUser.id, name: currentUser.name };
await new Promise((resolve) => provider.once('sync', resolve));
const room = await createDocumentCollaboration({
ydoc,
awareness,
documentId: 'room-1',
identity,
bootstrap: { kind: 'join' },
});
provider.on('status', ({ status }: { status: string }) => {
room.session.setTransportStatus(
status === 'connected' ? 'ready' : 'disconnected',
status === 'connected' ? undefined : 'transport-disconnected',
status === 'connected' ? undefined : 'websocket disconnected'
);
});
```
The second argument is a `CollaborationFailureCode`, not free text. Use
`transport-disconnected` for a socket that retries itself and
`authentication-failed` for a credential the server rejected. Those need
opposite responses from the host, so they must not share a code. Put your
provider's own wording in the third argument.
The factory rejects with a typed `CollaborationSchemaError`. Handle these
codes:
- `initialization-timeout`: No synchronized room appeared.
- `document-id-mismatch`: The room has a different `documentId`.
- `protocol-version-mismatch`: The room uses a different protocol version.
- `schema-version-mismatch`: The room uses a different schema version.
Use Yjs 13 on the server. The `y-websocket` server (`bin/server.cjs`) and
Hocuspocus support it. `@y/websocket-server` targets the Yjs 14 release
candidate. It synchronizes initial state and presence, but not live Yjs 13
updates. This incompatibility makes the document appear frozen.
### Recover from an error
An `error` session cannot repair itself. Recover it as follows:
1. Save the editor bytes with `await editor.save()`.
2. Call `room.destroy()`, then destroy the provider.
3. Create a new `Y.Doc`, `Awareness`, and provider.
4. Connect the provider.
5. Call `createDocumentCollaboration` with
`bootstrap: { kind: 'join' }`.
Keep the editor mounted with the saved bytes until the room is ready. If the
join fails, the saved bytes preserve the local work.
The same entry exports the experimental `createTextCollaboration`. It only
replicates paragraph text and rejects structural edits. Use
`createDocumentCollaboration`.
## Create a headless replica
`DocxEditor.createCollaborative` from `@docx-editor.dev/editor-api` opens a
Document Object Model (DOM)-free replica.
```ts
import { DocxEditor } from '@docx-editor.dev/editor-api';
import { createDocumentCollaboration } from '@docx-editor.dev/pro/collaboration';
const room = await createDocumentCollaboration({
ydoc,
awareness,
documentId: 'room-1',
identity: { actorId: 'agent', name: 'Agent', role: 'agent' },
bootstrap: { kind: 'join' },
});
const runtime = await DocxEditor.createCollaborative(room.document, room.session, {
author: 'Agent',
});
```
When the job finishes, call `runtime.dispose()` and then `room.destroy()` to release
the runtime and stop the replica.
## Let a server agent propose redlines
A background worker can join the same Hocuspocus room as the browser peers with
`role: 'agent'`, then attach `DocxEditor.createCollaborative` as above. The worker
owns its connection, so closing the initiating browser does not stop its job.
Set the tracking mode before editing to create Word tracked changes:
```ts
await runtime.run(async (context) => {
const matches = context.document.body.search('within 7 days', { matchCase: true });
matches.load('items');
await context.sync();
if (matches.items.length !== 1) throw new Error('Choose a unique target');
const range = matches.items[0]!;
range.load('text');
await context.sync();
// Decide from the loaded snapshot; sync refuses if the replica changed meanwhile.
context.document.changeTrackingMode = 'TrackMineOnly';
range.insertText('within 30 days', 'Replace');
await context.sync();
});
```
`range.insertText(text, 'Before' | 'After')`, `range.delete()`, and `range.clear()` use the same
transaction path. Configure an `author` on the runtime. `TrackMineOnly` persists across runs
and applies only to that server host. Peers retain their own editing mode. `Off` makes ordinary edits.
`TrackAll` and browser-host mode control refuse with `NotSupported`. The local mode is not saved as a
document-wide policy. Tracked edits currently support inline text within one paragraph, including table cells.
Targets touching pending revisions, structural changes, and formatting changes under tracking refuse atomically.
Commit one logical suggestion per sync. Send progress through a separate job/event
channel, while the collaboration session publishes committed redlines to peers.
On a stale snapshot, reread and reconsider the edit. A local revision check cannot
see remote changes that have not reached the worker yet.
The [server agent review example](https://github.com/eigenpal/docx-editor/tree/main/examples/server-agent-review)
includes a React review room, Hocuspocus persistence, a Node worker, scripted and
AI modes, snapshot tokens, deduplication, cancellation, and browser-independent
jobs. It also shows how to wait for outbound transport acknowledgement and clean
up both the runtime and the room. Browser peers need the review module to display
and accept or reject suggestions.
## Compose custom controls
Add custom controls as children of the collaboration root shown in
[Mount the editor](#mount-the-editor). The root supplies `key`, `document`,
`modules`, and the default author. Status and presence hooks inside it find the
session without a `session` argument.
In Vue, wrap the composable return with `reactive` before you pass it to
`DocxEditorCollaborationRoot`.
Let the room hook manage cleanup. An effect cleanup can destroy the room before
React StrictMode remounts the component.
## Collaboration behavior and limits
The WebRTC helper connects peers directly. A room exists while at least one
peer remains connected.
Attached replicas synchronize comments, tracked-change decisions, tables of
contents, and custom nodes. Named review actions include these operations:
- Add, reply to, resolve, and delete comments.
- Resolve tracked changes.
- Write package-scoped content, such as tables of contents and custom nodes.
The replica rejects two write paths. It rejects an edited ProseMirror document.
It also rejects review writes without a named intent.
The replica rejects tree edits when the session is destroyed, not ready, or
not attached. A transport interruption pauses editing until reconnection. To
continue editing, enable [`offlineEditing`](#edit-offline).
The session undo manager treats one typing run as one undo step.
One simultaneous run-formatting split converges without duplicate text. A later
split after one concurrent run-formatting round can duplicate text. All replicas
still converge on the same document.
### Numeric limits
Most exceeded limits produce a typed failure code.
| Limit | Value | Failure code | Remedy |
| ----------------------------------- | -------------- | ------------------------------------------- | ------------------------------------------------------ |
| Seed document | 20 MB | `baseline-too-large` | Reduce the document. |
| One embedded file | 32 MiB | `blob-too-large` | Compress the media. |
| All embedded files | 64 MiB | `blob-store-full` | Remove media and create a new room. |
| `actorId`, `name`, and `documentId` | 256 characters | `invalid-identity` or `invalid-document-id` | Shorten the value. |
| Presence participants | 256 | None | Keep the room below 256 participants. |
| Initial synchronization | 30 seconds | `initialization-timeout` | Connect the provider and confirm that the room exists. |
Presence reads return only the first 256 participants. For Hocuspocus, increase
`syncedTimeoutMs` when the initial synchronization needs more time.
### Watch a room's size
A room only grows. Deletion writes a tombstone, and deleted media bytes stay in
the shared state, so a long-lived room under heavy editing moves toward the
node and media limits. Crossing a limit is a terminal error for the room.
Watch the growth and archive the room before that happens.
`session.resourceUsage()` returns the replicated counts next to the limits:
```ts
const usage = session.resourceUsage();
if (usage.nodes > usage.maxNodes * 0.8) {
// Export the room and create a new one from the saved bytes.
}
```
On a server, call `readCollaborationResourceUsage(ydoc)` from
`@docx-editor.dev/pro/collaboration`. It reads a synchronized `Y.Doc` the same
way `readCollaborationDocument` does: it joins nothing and writes nothing.
Both probes walk the node map once per call. Read them on a schedule, not on
every edit.
For module registration and licensing, see the
[Pro package documentation](/docs/2.x/pro).
---
# Comments
Source: https://www.docx-editor.dev/docs/2.x/pro/comments
Comments attach a discussion to a text range.
The editor reads existing OOXML comments during load. It shows them beside the page and writes them during save.
You can continue threads created in Microsoft Word. Word can also continue threads created in this editor.
Comments share one sidebar, hook, and card layout with [tracked changes](/docs/2.x/pro/tracked-changes).
Comments require the review module from [`@docx-editor.dev/pro`](/docs/2.x/pro).
## Prerequisites
| Task | Requirement |
| ------------------------------------- | -------------------------------------------------------------------------------- |
| Read comments | A loaded document |
| Show the review rail | `reviewModule()` and `DocxEditorReview` |
| Create or reply in a browser | A non-empty `author`, `reviewModule()`, an editable mode, and an attached editor |
| Resolve, reopen, or delete in browser | `reviewModule()`, an editable mode, and an attached editor |
| Create or reply on a server | A non-empty `author` and a server runtime |
OOXML requires `w:author` for each comment and reply. The engine refuses a
write without an author.
For module registration and licensing, see the
[Pro package documentation](/docs/2.x/pro).
## Write a comment
The review hook's `comment(text, author?)` comments on the current selection.
It returns whether the editor applied the write. Keep the draft text when the method returns `false`.
This example keeps the draft after a refused write:
#### React
```tsx
import { useState } from 'react';
import { useReview } from '@docx-editor.dev/pro/react';
function CommentBox() {
const { comment, selectionAnchorY } = useReview();
const [text, setText] = useState('');
// null when nothing is selected: there is nowhere to anchor a comment.
if (selectionAnchorY === null) return null;
return (
);
}
```
#### Vue
```vue
```
Mount this component inside `DocxEditorRoot`.
`selectionAnchorY` is the proposed comment's document-space Y coordinate.
The engine calculates it without the DOM, as it does for card anchors. Use it to position a custom compose box beside the selection.
The packaged `DocxEditorReview.AddComment` and `DocxEditorReview.Draft` parts provide this workflow in Vue and React.
### Create a comment with editor-api
The Pro-licensed Office-shaped API creates the same canonical comment from an explicit range.
This example comments on the first search result:
```ts
const matches = context.document.body.search('payment terms');
matches.load('items');
await context.sync();
const comment = matches.items[0].insertComment('Confirm this with Legal.');
await context.sync();
```
Create the runtime with `{ author: 'Jess Lin' }`.
The write uses one package transaction and one browser **Undo** unit. Collapsed ranges create insertion-point comments.
The editor refuses cross-cell anchors and empty comment text. It does not change them to approximate values.
## Reply to a thread
`reply(item, text, author?)` adds a reply to a comment.
You can also reply to a revision. The editor creates a comment over the revision range because OOXML gives `w:ins` and `w:del` no body.
This example adds a fixed reply to any review item:
#### React
```tsx
function Thread() {
const { items, reply } = useReview();
return (
{items.map((item) => (
-
{item.text}
{item.author}
{item.date ? ` · ${item.date}` : ''}
{item.replyIds.length} replies
))}
);
}
```
#### Vue
```vue
-
{{ item.text }}
{{ item.author }}
{{ item.replyIds.length }} replies
```
Like `comment`, `reply` returns whether the editor applied the write. It does not throw for a refused write.
## Read threads
`useReview().items` contains comments and revisions. Filter by `kind` when you need one item type.
This example narrows the item type to comments:
#### React
```tsx
import type { ReviewItemView } from '@docx-editor.dev/pro/react';
function CommentsOnly() {
const { items } = useReview();
const comments = items.filter(
(item): item is Extract => item.kind === 'comment'
);
return (
{comments.map((c) => (
-
{/* File-derived. Render as text, never as markup. */}
{c.text}: {c.author}
))}
);
}
```
#### Vue
```vue
- {{ comment.text }}: {{ comment.author }}
```
Render comment text with interpolation. The text comes from the file.
Comment items include these fields:
| Field | Meaning |
| ---------------------------- | -------------------------------------------------- |
| `key`, `id` | Stable review and OOXML identifiers |
| `author`, `initials`, `date` | Comment author metadata |
| `text` | File-derived comment text |
| `replyIds` | Reply identifiers in the thread |
| `resolved` | Whether `w15:commentsEx` marks the thread complete |
| `parentId` | Parent comment identifier; absent on a thread root |
| `anchorY`, `pageIndex` | Document placement |
| `isActive` | Whether the caret is in the item |
The editor reads `initials` from `w:initials` when available. Otherwise, it derives initials from the author name.
For a document-level read without the review module, `editor.getComments()` returns `{ id, text, resolved }` for each thread.
## Resolve and reopen threads
The packaged card renders ` ` on an open comment and
` ` on a resolved one. Custom cards use the matching hook actions:
| Action | Result | Refusal behavior |
| ---------------------------- | ---------------------------------------------------- | ------------------------------------------- |
| `comment(text, author?)` | Adds a thread to the selection | Returns `false`; the caller keeps its draft |
| `reply(item, text, author?)` | Adds a reply, or comments on a revision range | Returns `false` |
| `resolve(item)` | Marks an open thread complete | Repeating it succeeds without a write |
| `reopen(item)` | Reopens a complete thread | Repeating it succeeds without a write |
| `remove(item)` | Deletes a comment thread or rejects a tracked change | Returns `false` when refused |
The hook does not own the caller's draft state.
`commentResolutionDisabledReason` explains why Resolve and Reopen are
unavailable.
This example selects the correct action for the comment state:
#### React
```tsx
function CommentDecision({ item }: { item: ReviewItemView }) {
const { resolve, reopen, commentResolutionDisabledReason } = useReview();
if (item.kind !== 'comment') return null;
return (
);
}
```
#### Vue
```vue
```
Both actions return whether the editor accepted the request. A stale item or non-comment item returns `false`.
Resolve on a resolved thread succeeds without changing the document. Reopen on an open thread behaves the same way.
These no-op actions do not create an **Undo** entry. Resolving writes the complete thread in one transaction.
One **Undo** action restores the prior state. Saving and reopening preserves the resolved state.
Viewing mode disables both actions. `commentResolutionDisabledReason` contains the engine refusal `the document is open for viewing`.
Use this value to explain why a custom control is disabled.
Editing and suggesting modes allow thread-state changes. Resolution changes metadata, not tracked document content.
## Listen for comment changes
Writing a comment or reply emits the editor's `change` event.
Use this event for autosave and dirty tracking:
```ts
useEditorEvent('change', (change) => void autosave(change.revision));
```
## Work with Word
The editor reads and writes author, initials, date, reply threading, and resolved state in the document's comments part.
A document annotated in this editor opens in Word with the same threads. Resolved threads remain resolved.
The editor reads and writes comments in headers, footers, footnotes, and endnotes within their own scopes.
## Next steps
- [Tracked changes](/docs/2.x/pro/tracked-changes): Use the full `useReview` API, sidebar parts, and filters.
- [Review colors and styling](/docs/2.x/pro/review-styling): Color comment authors and restyle the cards.
- [Vue composition](/docs/2.x/vue/composition): Arrange Vue editor and review parts.
- [React composition](/docs/2.x/react/composition): Arrange React editor and review parts.
- [Custom nodes](/docs/2.x/pro/custom-nodes): Add custom-node chrome.
---
# Custom nodes
Source: https://www.docx-editor.dev/docs/2.x/pro/custom-nodes
Use a custom node for an element DOCX has no type for: a citation, an @mention, a merge field, a
signature placeholder. Each is stored as an inline Word content control (`w:sdt`), so Word renders
its text and returns the control unchanged.
Requires the custom-nodes module from [`@docx-editor.dev/pro`](/docs/2.x/pro).
## Citation example
#### React
```tsx
import { z } from 'zod';
import { customNodesModule, defineCustomNode, insertCustomNode } from '@docx-editor.dev/pro';
import { CustomNodeChrome } from '@docx-editor.dev/pro/react';
import { DocxEditor, useDocxEditor } from '@docx-editor.dev/react';
const Citation = defineCustomNode({
name: 'citation',
tagPrefix: 'acme',
schema: z.object({
sourceId: z.string().min(1),
author: z.string(),
year: z.number().int(),
}),
text: (data) => `(${data.author} ${String(data.year)})`,
});
// Built once, outside render: modules are read when the editor is constructed.
const MODULES = [customNodesModule({ nodes: [Citation] })];
function CiteButton() {
const editor = useDocxEditor(); // null until the content is mounted
return (
);
}
export function Editor({ bytes }: { bytes: Uint8Array }) {
return (
console.log(Citation.dataOf(node))} />
);
}
```
#### Vue
```ts
// citation.ts
import { z } from 'zod';
import { customNodesModule, defineCustomNode } from '@docx-editor.dev/pro';
export const Citation = defineCustomNode({
name: 'citation',
tagPrefix: 'acme',
schema: z.object({
sourceId: z.string().min(1),
author: z.string(),
year: z.number().int(),
}),
text: (data) => `(${data.author} ${String(data.year)})`,
});
// Built once, outside setup: modules are read when the editor is constructed.
export const MODULES = [customNodesModule({ nodes: [Citation] })];
```
```vue
```
```vue
```
Insert places `(Smith 2024)` at the caret as a chip. After you save, open the file in Word, and
reopen it here, the editor recognizes the chip and its payload again.
## Common tasks
A payload validated by your schema, up to 262,144 UTF-16 code units, kept in a customXml part.
`CustomNodeChrome` paints recognized nodes and exposes click and hover handlers and the node
rect.
Three functions, one transaction and one undo step each.
`customNodesOf(editor)` in body order, `dataOf` to get your type back.
`saveForExport` applies each definition's `preserveOnExport`.
`customNodeXml` returns control XML plus the required package metadata.
Every option on `defineCustomNode`, every refusal code, the on-disk format.
## Define a node
```ts
const Citation = defineCustomNode({
name: 'citation', // second segment of the tag
tagPrefix: 'acme', // this definition claims acme:*
schema: CitationData, // shape of the payload
text: (data) => `(${data.author} ${String(data.year)})`, // what the document shows
});
```
`schema` is any [Standard Schema](https://standardschema.dev): zod, valibot, arktype. The pro
package bundles none of them, so install the schema library your project already uses.
- Invalid data is rejected before anything is written to the document.
- Data read back out is returned as your type instead of `unknown`.
Payloads also arrive from `.docx` files someone else wrote, so the schema validates that JSON
before your code uses it. Async validation is refused.
`text` reads the schema's _output_, so a `.default()` or a `.transform()` has already run.
A node can skip `schema` and keep everything in its tag, which Word caps at 64 characters. See
[nodes without a payload](/docs/2.x/pro/custom-nodes-reference#nodes-without-a-payload).
Modules are read once, at construction. Changing the array after construction has no effect; remount the Root.
Everything else is optional: `label` and `chrome.color` for the chip, `onClick` / `onHover` /
`onEdit`, `reviewCard` for a sidebar card, `preserveOnExport` for export behavior. See the
[reference](/docs/2.x/pro/custom-nodes-reference#definecustomnode).
## Render the chips
Chips are content-locked by default, so editing runs through the context menu:
Mount both components inside the viewport:
#### React
```tsx
import { CustomNodeChrome, CustomNodeContextMenu } from '@docx-editor.dev/pro/react';
openPopover(node)} />
openEditForm(node)} />
;
```
#### Vue
```vue
```
Both entries export the same component names.
An activated node carries `name`, `attrs`, `tag`, `data` and `rect` (viewport-relative, for
anchoring your own popover), plus `nodeId` and `text` when they resolve.
Remove can be refused, by a locked wrapper or a document open for viewing. Pass
`onRemoveRefused(node, reason)` to show the engine's reason; without it, the menu closes.
## Insert, update, remove
```ts
import { insertCustomNode, removeCustomNode, updateCustomNode } from '@docx-editor.dev/pro';
insertCustomNode(editor, Citation, { data }); // at the caret, or at input.at
updateCustomNode(editor, Citation, nodeId, { data }); // rewrites in place
removeCustomNode(editor, nodeId); // node and payload
```
All three writes use one transaction and create one undo step, including payload changes. Successful
inserts and updates return `{ ok: true, changed: true, nodeId }`; refusals include a `reason` and a
`code`.
An update replaces the control and returns its new node ID. The ID passed to `updateCustomNode` no
longer resolves, so update any state that held it:
```ts
const result = updateCustomNode(editor, Citation, card.nodeId, { data: next });
if (result.ok && result.nodeId) setCard({ ...card, nodeId: result.nodeId });
```
A payload the schema rejected returns with `issues`, each pointing at a field:
```ts
const result = insertCustomNode(editor, Citation, { data: form });
if (!result.ok) {
for (const issue of result.issues ?? []) {
setFieldError(issue.pointer, issue.message); // 'year', 'authors.0'
}
}
```
`attrs`, `lock`, `alias`, `at` and the three refusal codes are in the
[reference](/docs/2.x/pro/custom-nodes-reference#writes).
## Read the nodes
```ts
import { customNodesOf } from '@docx-editor.dev/pro';
for (const node of customNodesOf(editor)) {
const citation = Citation.dataOf(node);
if (citation) index.add(citation.sourceId); // typed
}
```
`customNodesOf` returns recognized nodes from the document body in document order. It excludes
headers, footers, footnotes and endnotes. Because each call reads the current document and no change
event exists, call it again after every edit. Do not retain the returned array.
Chrome and read surfaces carry every definition's nodes under one type, so their `data` is
`unknown`. `dataOf` narrows a node to this definition and validates its payload, so you never write
a parse at the call site:
#### React
```tsx
open(Citation.dataOf(node))} />
```
#### Vue
```vue
```
Inside `text`, `reviewCard` and `fromDocx`, `data` is already the schema's output type.
`data` is `undefined` when the node carries no payload, when its binding names a store node the
document does not hold, or when the payload fails the schema. The last two report through
`onDiagnostic`:
```ts
customNodesModule({
nodes: [Citation],
onDiagnostic: ({ code, name, nodeId, issues }) => {
// code: 'payload-invalid' | 'payload-missing'
console.warn(`${name} ${nodeId}: ${code}: ${issues.join(', ')}`);
},
});
```
## Save vs export
Use `editor.save()` for the copy you store. Word and Word Online preserve the node's text, lock, tag,
binding and payload.
Use `saveForExport` when recipients must not receive custom-node markup, such as internal
annotations. Generate that external copy separately from the saved document.
```ts
import { saveForExport } from '@docx-editor.dev/pro';
// The copy you keep. Reopens here with the chips working.
await storage.put(docId, new Uint8Array(await editor.save()));
// The copy that leaves. Notes removed, citations flattened to their words.
const outgoing = await saveForExport(editor);
if (!outgoing.ok) throw new Error(outgoing.reason);
download(outgoing.bytes);
```
`preserveOnExport` decides per definition: `true` (default) keeps the full control, `'text'` keeps the words
and drops the control, `false` removes the node and its content. A tag no definition claims is never
touched.
Store the saved bytes, not the exported ones. What the export stripped is gone: unwrapped text does
not become a node again.
`saveForExport` needs an editor. On a server, `prepareForExport(bytes, definitions)` is the same
pipeline over bytes with no DOM. A definition you do not pass is not touched. See
[export](/docs/2.x/pro/custom-nodes-reference#export).
## Common configurations
**A mention, no payload.** Everything fits in the tag, so there is no schema and no customXml part.
`fromDocx` clamps the untrusted strings a `.docx` supplies, or returns `null` to leave the control
literal:
```ts
const Mention = defineCustomNode({
name: 'mention',
tagPrefix: 'acme',
fromDocx: ({ attrs }) => (attrs['userId'] ? attrs : null),
});
```
**An internal note that must not leave.** Kept on save, removed on export:
```ts
const InternalNote = defineCustomNode({
name: 'note',
tagPrefix: 'acme',
schema: NoteData,
text: (data) => data.body,
preserveOnExport: false,
});
```
**A node written on a server.** `customNodeXml` builds the same control as XML without an editor or
DOM. For a payload-bearing node, server authoring must also add the payload package parts, their
relationships and the properties part's content-type override. When the document opens in this
editor, the node is recognized:
```ts
import { customNodeXml } from '@docx-editor.dev/pro';
const built = customNodeXml(Citation, { sourceId: 'smith-2024' }, '(Smith 2024)', {
data: { sourceId: 'smith-2024', author: 'Smith', year: 2024 },
});
if (built.ok) template.replace('{{citation}}', built.xml);
```
`built.store` returns two parts, two relationships and one content-type override. Add all five items
or Word offers to repair the file. See
[server-side authoring](/docs/2.x/pro/custom-nodes-reference#server-side-authoring).
**A card in the review sidebar.** `reviewCard` contributes one card per node, anchored at its range.
`useReviewItem()` scopes your content to the card it renders inside:
#### React
```tsx
import { DocxEditorReview, useReviewItem } from '@docx-editor.dev/pro/react';
function CitationActions() {
const item = useReviewItem();
if (item?.kind !== 'custom') return null;
return ;
}
;
```
#### Vue
```vue
```
This needs the review module registered alongside the custom-nodes module.
## Next steps
- [Custom nodes reference](/docs/2.x/pro/custom-nodes-reference): every parameter, every error code,
the on-disk format
- [Runnable example](https://github.com/eigenpal/docx-editor/tree/main/examples/custom-nodes):
define, insert, edit, and round-trip a citation with a payload
- [Content controls](/docs/2.x/guides/content-controls): the built-in control surface these build on
---
# Custom nodes reference
Source: https://www.docx-editor.dev/docs/2.x/pro/custom-nodes-reference
The API surface of [custom nodes](/docs/2.x/pro/custom-nodes). Start with the guide if you have not
built one yet.
A node stores data in two places:
| Location | Holds | Limit |
| ----------- | ------------------- | -------------------------------------------------- |
| `w:tag` | The node's identity | 64 characters, including the prefix and name |
| The payload | Everything else | 262,144 UTF-16 code units, about 256 KiB for ASCII |
The payload is a customXml data part that the control binds to through `w:dataBinding`. Word
preserves both.
## defineCustomNode
### Identity
| Parameter | Type | Description |
| ----------- | -------- | ---------------------------------------------------------- |
| `name` | `string` | Node type name. The second segment of the tag. |
| `tagPrefix` | `string` | The prefix this definition claims. `docx` claims `docx:*`. |
### Payload and text
| Parameter | Type | Description |
| --------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `schema` | zod (or any [Standard Schema](https://standardschema.dev)) | Shape of the payload. The package does not include a schema library; supply your own. A schema that validates asynchronously is refused. |
| `text` | `(data) => string` | What the document shows, from the payload. Reads the schema's output type. |
### Chrome
| Parameter | Type | Description |
| --------------------- | ------------------------------------------------------ | -------------------------------------------- |
| `label` | `string` | Display name for chrome. Defaults to `name`. |
| `chrome.color` | `string` | Chip tint and border. |
| `reviewCard` | `({ attrs, text, data }) => { title, detail } \| null` | Contributes a sidebar card per node. |
| `onClick` / `onHover` | `(node: ActivatedCustomNode) => void` | Pointer events on the painted chip. |
| `onEdit` | `(node: ActivatedCustomNode) => void` | The context menu's "Edit \{label}" row. |
### Export and interoperability
| Parameter | Type | When |
| ------------------ | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `preserveOnExport` | `boolean \| 'text'` | Controls whether export keeps, unwraps or removes the node. See [preserveOnExport](#preserveonexport). |
| `tagAttrs` | `(data) => Record` | A reader without the payload store should still identify the node. |
| `payloadNamespace` | `string` | You need a specific customXml namespace. Defaults to one from `tagPrefix`. It goes into an XPath prefix declaration, so quotes, angle brackets and ampersands are rejected. |
| `fromDocx` | `({ attrs, text, data }) => attrs \| null` | The node has **no** schema and keeps its data in the tag, or you need to leave a control unrecognized by returning `null`. |
The returned `CustomNode` carries [`dataOf`](#dataof) alongside the definition.
## customNodesModule
```ts
customNodesModule({ nodes: [Citation], onDiagnostic });
```
| Option | Type | Description |
| -------------- | ------------------------------------------ | ------------------------------------------------------------- |
| `nodes` | `readonly AnyCustomNodeDefinition[]` | The definitions this editor recognizes. |
| `onDiagnostic` | `({ code, name, nodeId, issues }) => void` | `'payload-invalid'` or `'payload-missing'` on a node it read. |
| `licenseKey` | `string` | Never validated at construction, never touches the network. |
The listener belongs to the editor the module is registered on. Each editor invokes only its own
listener. Disposing the editor removes that listener.
## Writes
| Function | Behavior |
| ----------------------------------------------- | --------------------------------------- |
| `insertCustomNode(editor, def, input)` | Inserts at the caret, or at `input.at`. |
| `updateCustomNode(editor, def, nodeId, update)` | Rewrites in place. |
| `removeCustomNode(editor, nodeId)` | Deletes the node and its payload. |
Each is one transaction and one undo step, payload included.
### Input
| Field | Type | Description |
| ------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `data` | The schema's input type | The payload. Validated before anything is written. |
| `attrs` | `Record` | Tag attrs. Derived by `tagAttrs` when declared. Passing it overrides the derivation. |
| `text` | `string` | Document text. Derived by `text` when declared. Passing it overrides the derivation. |
| `at` | `{ paragraphId, offset }` | Insert position. Defaults to the caret. `insertCustomNode` only. |
| `lock` | `false \| 'sdtLocked' \| 'sdtContentLocked' \| 'contentLocked'` | Defaults to `contentLocked`. |
| `alias` | `string` | `w:alias`, the title Word shows on the control. |
`updateCustomNode` keeps the payload you do not mention. Pass `data: null` to remove one.
`contentLocked` prevents inline text editing while leaving the node deletable as a unit, in the
editor and in Word. A node carrying a payload is uneditable regardless of `lock`: the engine refuses
content edits inside a bound control, and so does Word.
### Returns
`{ ok: true, changed: true, nodeId }`, or a refusal.
`nodeId` names the control the write authored. A rewrite replaces the control rather than editing
it, so the id passed to `updateCustomNode` names nothing afterwards. `removeCustomNode` authors no
control, so it returns no `nodeId`.
A refusal carries a `code`:
| `code` | Meaning |
| ------------- | --------------------------------------------------------------------------------------------------------------- |
| `invalidArgs` | Caused by invalid arguments: a payload past the cap, a tag over 64 characters, an offset outside the paragraph. |
| `unsupported` | Blocked by document state: a lock, a protected form, viewing mode. |
| `notFound` | No document is mounted, or `updateCustomNode` was given an id no node has. |
A payload the schema rejected also carries `issues`:
| Field | Type | Description |
| --------- | ------------------------------- | ------------------------------------- |
| `message` | `string` | Validation message from the schema. |
| `path` | `readonly (string \| number)[]` | Route to the field: `['authors', 0]`. |
| `pointer` | `string` | The same path joined: `authors.0`. |
## Reads
### customNodesOf
`customNodesOf(editor, options?)` reads the body and recognizes every definition registered on the
editor, in document order. Pass `{ nodes }` to narrow it. It derives from the document each time it
is called. There is no change event, so re-read after an edit rather than holding the array.
A payload with no `schema` returns as parsed JSON on a null-prototype object, so
`hasOwnProperty` and `instanceof Object` do not hold on it.
### dataOf
`Citation.dataOf(node)` returns the schema's output type, or `undefined` for a different
definition's node, one with no payload, or one whose payload the schema rejects. A `name` is checked
when the object has one and never required, so it also works on your own state:
```ts
const survey = Citation.dataOf(popoverState); // { data } is enough
```
Inside `text`, `reviewCard` and `fromDocx`, `data` is already the schema's output type. Everywhere
else it is `unknown`, because those surfaces carry every definition's nodes under one type.
A definition does not need `reviewCard` to be read. A node without one is still recognized and still
carries its payload; it contributes nothing to the sidebar.
### Review items
Nodes with ranges, for anchoring UI. Requires the review module:
```ts
const cards = editor.getReviewItems().filter((entry) => entry.item.kind === 'custom');
```
## Export
| Destination | Call | Custom nodes |
| ---------------- | ----------------------- | --------------------------------------------- |
| Internal storage | `editor.save()` | Kept intact. |
| External copy | `saveForExport(editor)` | Each definition's `preserveOnExport` decides. |
`saveForExport` calls `editor.save()`, then applies every definition registered in `modules`.
`options.nodes` narrows the definitions and leaves all others untouched. The result reports how many
controls each policy changed in `unwrapped` and `removed`. A refusal returns a `reason` and no bytes.
`destination` defaults to `'external'`. Set it to `'internal'` to return the saved bytes unchanged
when one code path handles both destinations:
```ts
const copy = await saveForExport(editor, {
destination: keepOurMarkup ? 'internal' : 'external',
});
```
### preserveOnExport
`preserveOnExport` applies per definition. `editor.save()` always keeps nodes intact.
| Value | External export result |
| ---------------- | ----------------------------------------------------------------------- |
| `true` (default) | Keeps the control, tag, binding, payload and text. |
| `'text'` | Keeps the text and removes the control, binding and associated payload. |
| `false` | Removes the control and its content, including the associated payload. |
Use `'text'` when recipients need the visible value without your custom-node markup:
```ts
const Citation = defineCustomNode({
name: 'citation',
tagPrefix: 'docx',
preserveOnExport: 'text',
});
```
One document can use all three settings. Export applies each definition independently and never
touches a tag that no supplied definition claims.
A node that leaves takes its payload with it, even when other nodes in the same store remain. When
the last node for a namespace is gone, both `customXml` parts, both relationships and the
content-type override are removed.
Every story is covered, so a chip in a header is treated like a chip in the body.
Store the saved bytes, not the exported bytes. Exported text does not become a node again.
This removes markup written by this library. It does not touch `docProps/app.xml`,
`docProps/core.xml`, comment and revision authors, rsids, or custom document properties. It does not
anonymize a document.
### prepareForExport
`saveForExport` needs an editor. Where there is none, `prepareForExport` is the same pipeline over
bytes, with the definitions spelled out. It touches no DOM:
```ts
import { prepareForExport } from '@docx-editor.dev/pro';
// A document your backend generated with customNodeXml, stripped before it is sent.
const generated = await renderContract(order);
const outgoing = prepareForExport(generated, [Clause, InternalNote]);
if (!outgoing.ok) throw new Error(outgoing.reason);
await email.attach(outgoing.bytes);
```
List every definition whose nodes might be in the document. **A definition you do not pass is not
touched**, and an unmatched node remains unchanged.
`customNodeXml` and `prepareForExport` run without DOM globals, so a backend can author, store and
strip custom nodes in Node.
## Server-side authoring
`customNodeXml(definition, attrs, text, options)` builds the same content control as XML, with no
editor and no DOM. A node written on a server is recognized identically when the document opens in
the editor.
```ts
import { customNodeXml } from '@docx-editor.dev/pro';
const built = customNodeXml(Citation, { sourceId: 'smith-2024' }, '(Smith 2024)', {
data: { sourceId: 'smith-2024', locator: 'p.14', authors: ['Smith, J.'], year: 2024 },
});
if (built.ok) {
template.replace('{{citation}}', built.xml);
}
```
For a payload-bearing node, `built.store` returns both `customXml` parts, both relationships and the
content-type override. Add every item to the package. Word offers to repair a document when a
control's binding names a missing store.
`encodeCustomNodeTag` and `decodeCustomNodeTag` are the tag codec if you need to read or write the
identity yourself. Tags are capped at `MAX_TAG_LENGTH`.
## Nodes without a payload
A node can skip `schema` and keep everything in the `w:tag`, which Word caps at 64 characters. Then
`attrs` is all it has, and `fromDocx` is where you clamp those untrusted strings or return `null` to
leave the control literal:
```ts
const Mention = defineCustomNode({
name: 'mention',
tagPrefix: 'docx',
fromDocx: ({ attrs }) => (attrs['userId'] ? attrs : null),
});
```
Writes then pass `attrs` and `text` themselves, since there is no payload to derive from.
`attrs` and `text` reaching `fromDocx` come from the `.docx`, which the sender controls end to end.
They are rendered as text, never as markup; do not build URLs or DOM from them without sanitizing.
`data` has been validated against `schema`.
## Word round-trip
A recognized node is an ordinary inline content control. Word renders its text, honors the lock, and
preserves the tag, the binding and the payload. A document edited in Word and reopened here is
recognized from the same tag. If a Word user edited the text of an unbound node despite the lock,
`fromDocx` receives the changed text.
A document opened without your definition registered renders the control's content literally, as
Word does. Nothing is lost.
A bound control is read-only in Word: Word renders its text from the payload and does not accept
typing into it.
## Payload lifecycle
| Event | Result |
| --------------------------------- | ----------------------------------------------------------------------------------- |
| `removeCustomNode` | The payload is removed in the same transaction. |
| A control deleted in Word | Collected on the next open, by reconciling against what the document binds. |
| `updateCustomNode` with `data` | Label and payload are written together. |
| `updateCustomNode` without `data` | The payload is carried forward under the new label. |
| A chip cut or copied | The clipboard carries the chip's text, not the control. Pasting inserts plain text. |
The open-time sweep is not undoable: it collects payloads whose control was already gone when the
document arrived.
## On-disk format
`customXml/item1.xml`:
```xml
{"sourceId":"smith-2024","locator":"p.14","authors":["Smith, J."],"year":2024}
```
The control in `word/document.xml` that binds it:
```xml
```
`w:storeItemID` matches `ds:itemID` in `customXml/itemProps1.xml`. The store is reached through the
`customXml` relationship on the story part; that pair is what picks one store out of several.
Node ids are `cx1`, `cx2`, … derived from the store's current contents, so writing the same document
twice produces the same bytes. One store per `payloadNamespace`: two definitions sharing a
`tagPrefix` share a store.
Payloads are capped at 262,144 UTF-16 code units (`MAX_CUSTOM_NODE_DATA_LENGTH`) on write and on
read; labels at 4096 on write. Keys named `__proto__`, `constructor` and `prototype` are stripped
from a parsed payload at every depth. A legitimate field with one of those names therefore arrives
missing, and a schema requiring it fails with "expected string, received undefined".
## Limits
- No in-place edit dialog. Re-authoring is `updateCustomNode` with a form you supply. The activation
carries `nodeId`, `text` and `data` to prefill it.
- A bound node cannot be edited in Word. Making one editable requires a two-way binding, which is
not implemented.
- The clipboard carries text only. Cutting a chip and pasting it produces plain text, not a node.
---
# 2.x/pro/index
Source: https://www.docx-editor.dev/docs/2.x/pro/index
`@docx-editor.dev/pro` adds these review capabilities to the Vue and React editors:
- [**Tracked changes**](/docs/2.x/pro/tracked-changes): Use suggesting mode, render markup, and accept or reject changes.
- [**Comments**](/docs/2.x/pro/comments): Add threads and replies to a text range.
- [**Review colors and styling**](/docs/2.x/pro/review-styling): Color review content by author or by change type.
- [**Custom nodes**](/docs/2.x/pro/custom-nodes): Store your inline node types as Word content controls.
- [**DOCX collaboration reference**](/docs/2.x/pro/collaboration): Configure rooms, providers, presence, and recovery.
Both adapter entries export custom-node chip and context-menu chrome.
## Install
Install the Pro package with your adapter and the core engine:
#### React
```bash
npm install @docx-editor.dev/react @docx-editor.dev/core @docx-editor.dev/pro
```
Import the review rail from `@docx-editor.dev/pro/react`.
#### Vue
```bash
npm install @docx-editor.dev/vue @docx-editor.dev/core @docx-editor.dev/pro
```
Import the review rail from `@docx-editor.dev/pro/vue`.
## Register a module
Pass review capabilities to the editor root through `modules`.
The editor registers modules during creation. Keep the module array stable across renders.
This example registers `reviewModule` and adds the review rail:
#### React
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import { reviewModule, DocxEditorReview } from '@docx-editor.dev/pro/react';
// Module array built once, outside render.
const MODULES = [reviewModule()];
export function Reviewer({ bytes }: { bytes: Uint8Array }) {
return (
{/* The review sidebar: tracked changes and comments as cards beside the page. */}
);
}
```
#### Vue
```vue
```
The packaged `DocxEditor` component also accepts `modules` in each adapter.
Without a review module, the editor still opens documents that contain revisions and comments.
The editor saves that review content without changes. It renders revisions in their final state and provides no review interface.
Register the module to show review content and provide review actions.
## Author attribution
Comments and tracked changes require an author. Office Open XML (OOXML) requires this value.
Set `author` on the root.
#### React
```tsx
```
#### Vue
```vue
```
Write calls return whether the editor applied the change. They do not throw for an unavailable author.
For example, a reply without an author returns `false`. The editor does not create an invalid document.
## Choose the review rail or composables
`useReview()` provides all capabilities in the packaged review rail. Use the rail, or render your own interface.
This example shows the number of pending items:
#### React
```tsx
import { useReview } from '@docx-editor.dev/pro/react';
function ChangeCount() {
const { items, ready } = useReview();
if (!ready) return null;
return {items.length} pending;
}
```
React hooks return the values directly.
#### Vue
```vue
{{ pending }} pending
```
Vue composables return computed refs, so read them with `.value` in script code.
For the complete composable API, see [Tracked changes](/docs/2.x/pro/tracked-changes).
To arrange editor parts, see [Vue composition](/docs/2.x/vue/composition) or [React composition](/docs/2.x/react/composition).
## Licensing
This package is licensed under the [EigenPal Pro License](https://github.com/eigenpal/docx-editor/blob/main/packages/pro/LICENSE.md), and you can compare and buy license and support levels on the [pricing page](https://www.docx-editor.dev/pricing).
Both module factories accept an optional `licenseKey`.
Module construction does not validate the key or access the network. This example passes a key to `reviewModule`:
```ts
const MODULES = [reviewModule({ licenseKey: process.env.NEXT_PUBLIC_DOCX_LICENSE })];
```
For a Vue application, read the key from your build tool instead of `process.env`.
## Next steps
- [Tracked changes](/docs/2.x/pro/tracked-changes): Configure suggesting mode and the review interface.
- [Comments](/docs/2.x/pro/comments): Add, reply to, resolve, and reopen comment threads.
- [Review colors and styling](/docs/2.x/pro/review-styling): Give each reviewer a color and an avatar.
- [Custom nodes](/docs/2.x/pro/custom-nodes): Add custom-node chrome.
- [Vue composition](/docs/2.x/vue/composition): Arrange Vue editor parts.
- [React composition](/docs/2.x/react/composition): Arrange React editor parts.
---
# Review colors and styling
Source: https://www.docx-editor.dev/docs/2.x/pro/review-styling
Review styling changes presentation only. It does not change the document file.
The same author color applies to [tracked changes](/docs/2.x/pro/tracked-changes)
and [comments](/docs/2.x/pro/comments).
## Default behavior
| Behavior | Result |
| ----------------------- | ---------------------------------------------------------------- |
| Author assignment | Authors receive slots in first-appearance order. |
| Color ramp | `--doc-review-author-0` through `--doc-review-author-7` |
| More than eight authors | The ramp repeats after eight authors. |
| Change type | Insertions stay underlined. Deletions stay struck through. |
| Review sidebar | Cards use the author color for the leading edge and avatar disc. |
| Comment highlights | All authors use the same yellow highlight by default. |
Override ramp tokens under `.docx-editor`. Load your stylesheet after
`editor.css`, or use a more specific selector.
```css
.docx-editor {
--doc-review-author-0: #7c3aed;
--doc-review-author-1: #0e7490;
}
```
The document filter handles dark mode. Review chrome adjusts separately.
## Choose a styling API
| API | Use |
| ------------------------------- | ---------------------------------------------------------------- |
| CSS tokens | Replace the shared eight-color ramp. |
| `AuthorStyle` declaration | Set one author's color, background, classes, or avatar. |
| `ColorByChangeType` declaration | Color unmatched insertions green and deletions red. |
| `setRevisionStyles` | Control the same state from an editor instance or headless host. |
Use declarations or `setRevisionStyles` as the source of style state. Calling
`setRevisionStyles` replaces mounted declarations until a declaration changes.
Declarations do not render Document Object Model (DOM) elements. Mounting,
changing, or removing one repaints the editor without resetting selection,
caret position, or undo history.
### Declare author styles
Place declarations anywhere inside the editor root.
#### React
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import { reviewModule } from '@docx-editor.dev/pro/react';
const MODULES = [reviewModule()];
;
```
#### Vue
```vue
```
`author` must match the document's `w:author` value. An absent author has no
effect. Unmatched authors keep ramp colors unless `ColorByChangeType` is mounted.
| Author style field | Effect |
| ----------------------------------- | ------------------------------------------------- |
| `color` | Sets document ink, decorations, and card accents. |
| `background` | Sets the background tint behind changes. |
| `spanClassName` / `span-class-name` | Adds classes to painted change spans. |
| `avatarUrl` / `avatar-url` | Sets the review sidebar avatar. |
Keep span CSS metric-safe. Do not change font size, weight, or family. Such
changes make painted text differ from measured layout.
### Set styles through the editor
`setRevisionStyles` and the editor creation option accept a `RevisionStyles`
value. Declarative components accept individual author or change-type props.
Set `others` to `'author'`, the default, or `'kind'` in `RevisionStyles`.
```ts
editor.setRevisionStyles({
others: 'kind',
authors: {
'Jess Lin': '#7c3aed',
'Sam Reyes': { color: '#0e7490', avatarUrl: '/avatars/sam.png' },
},
});
```
An author value can be a color string or an author style object. A headless host
can also pass `revisionStyles` during editor creation.
## Read document authors
Use `useReviewAuthors()` to build legends and color controls. Use
`getReviewAuthors()` without an adapter.
| Returned behavior | Detail |
| ------------------- | ---------------------------------------------------------------------------------------- |
| Order | Tracked-change authors appear first. Comment-only authors follow. |
| Updates | The list updates after document load or review style changes. |
| `slot` | This unbounded rank identifies first-appearance order. |
| `color` | This is the resolved card color. An unmatched value can be `var(--doc-review-author-N)`. |
| Resolved view | Authors with only hidden revisions are omitted unless they also commented. |
| `ColorByChangeType` | Page colors show change types. Returned colors still describe card accents. |
React returns the list directly. Vue returns a shallow ref. Call the composable
under `DocxEditorRoot`.
Add `.docx-editor` to a legend or its ancestor. This class resolves ramp token
values used by swatches.
## Add avatars
Set `avatarUrl` to replace initials in a review card. Authors without an avatar
keep their initials.
| Avatar behavior | Result |
| ------------------ | ------------------------------------------------------------ |
| Loading or failure | The author color remains visible under the image. |
| Rejected URL | The card uses initials. `useReviewAuthor` reports no avatar. |
| Document page | The avatar does not affect painted document content. |
| Request policy | The packaged image uses `referrerPolicy="no-referrer"`. |
Use a host you control. The editor accepts application-held `blob:` URLs and
non-SVG `data:image/*` URLs. It rejects other `data:` URLs, SVG data images,
script schemes, and protocol-relative hosts.
The browser fetches an avatar when its card renders. The editor never loads an
avatar address from the document.
Document authors are untrusted strings. A sender can use a known name and
receive its configured image. Do not use review styling as identity proof.
## Use CSS hooks
| Element | Author hooks |
| ----------------------- | ---------------------------------------------------- |
| Tracked-change spans | `data-review-author`; slot only with author coloring |
| Paragraph-mark pilcrows | `data-review-author`, `data-review-author-slot` |
| Comment highlight bands | Both attributes and `--doc-review-author-current` |
| Review cards | Both attributes and `--doc-review-author-current` |
| Hover balloons | Both attributes and `--doc-review-author-current` |
| Gutter markers | Both attributes and `--doc-review-author-current` |
`data-review-author` contains the exact document value.
`data-review-author-slot` wraps to `0` through `7`. Calculate it as `slot % 8`
when you build selectors from `useReviewAuthors()`.
Painted spans use an inline ink color. CSS cannot override that color or its text
decoration. Use a declaration or ramp token for ink. Use `currentColor` for
metric-safe span effects.
```css
.docx-editor [data-review-author='Jess Lin'] {
outline: 1px dotted currentColor;
outline-offset: 1px;
}
.docx-editor .docx-comment-band {
background: color-mix(
in srgb,
var(--doc-review-author-current, var(--doc-comment-bg)) 22%,
transparent
);
}
```
`--doc-review-author-current` contains the light-theme value in both themes.
Define a dark-theme rule when you tint comment bands by author.
## Build custom review cards
`useReviewAuthor` returns one author's resolved color, ramp slot, and declared
style. React returns the value directly. The Vue composable accepts a ref or
getter and returns a computed ref.
The `DocxEditorReview.List` callback or Vue `#item` slot receives each item and
its `author`. Use that value to select a custom card. For composition patterns,
see [React composition](/docs/2.x/react/composition) or
[Vue composition](/docs/2.x/vue/composition).
## Next steps
- [Tracked changes](/docs/2.x/pro/tracked-changes): Record and resolve revisions.
- [Comments](/docs/2.x/pro/comments): Add discussion threads.
---
# Tracked changes
Source: https://www.docx-editor.dev/docs/2.x/pro/tracked-changes
Suggesting mode records edits as Word revisions. The editor underlines
insertions and strikes through deletions.
Each revision keeps available author and timestamp metadata. OOXML timestamps
are optional. Saved files use Word `w:ins` and `w:del` markup.
This feature requires the review module from
[`@docx-editor.dev/pro`](/docs/2.x/pro).
## Choose an editing mode
| Root `mode` value | Engine state | Behavior |
| ----------------- | -------------- | ------------------------------------- |
| `'edit'` | `'editing'` | Applies edits without revisions. |
| `'suggesting'` | `'suggesting'` | Records supported edits as revisions. |
| `'view'` | `'viewing'` | Keeps the document read-only. |
Use the `setEditingMode` command to change the mode after mount. The packaged
toolbar exposes the same command in `review.editingMode`.
If you omit `mode`, the root honors `w:trackRevisions` when a review module and
an `author` are present. An explicit `mode` overrides this document setting.
The `` convenience component defaults to `mode="edit"`. Enforced
document protection can also restrict the available mode.
### Suggesting needs an author
A revision records who proposed it, so suggesting mode needs an `author`. If you
enable suggesting without one, the editor refuses the request instead of
accepting focus and ignoring keystrokes:
- `setEditingMode('suggesting')` returns `{ ok: false, code: 'invalidArgs' }`
with the reason. `can` reports the same reason. The toolbar disables the
**Suggesting** item and shows the reason. Other permitted modes stay available.
- A `mode="suggesting"` prop without an `author` opens in editing mode.
- Each case publishes the reason as `lastRejection` in the editor state.
- The editor raises the configuration error once per instance through the
`error` event, with the code `suggestingNeedsAuthor`, and logs the same
message to the console once.
Set the author at mount, or later with `setAuthor` or the `author` prop. When
the author arrives, the pending request enters suggesting mode, unless the
reader has already chosen a mode. Calls to `setEditingMode` count as reader
choices, including calls from `onReady`.
If you remove the author while `mode="suggesting"` is active, the editor
returns to editing mode and publishes the reason, unless document protection
forbids editing. Suggesting adopted from a document or chosen through
`setEditingMode` stays active, refuses edits, and publishes the reason until
the author returns.
This example switches between editing and suggesting:
#### React
```tsx
import { DocxEditor, useEditorCommand, useEditorState } from '@docx-editor.dev/react';
import { DocxEditorReview, reviewModule } from '@docx-editor.dev/pro/react';
const MODULES = [reviewModule()];
function SuggestToggle() {
const mode = useEditorState((state) => state.editingMode);
const suggest = useEditorCommand({ type: 'setEditingMode', mode: 'suggesting' });
const edit = useEditorCommand({ type: 'setEditingMode', mode: 'editing' });
const active = mode === 'suggesting';
return (
);
}
export function Reviewer({ bytes }: { bytes: Uint8Array }) {
return (
);
}
```
#### Vue
```vue
```
```vue
```
## Tracked content
| Change | Support and display |
| -------------------- | ---------------------------------------------------------------------------- |
| Text | Tracks insertions, deletions, and replacements. A replacement uses one card. |
| Paragraph structure | Tracks inserted and deleted paragraph marks. |
| Paragraph properties | Tracks alignment, indents, spacing, and style in a separate card. |
| Tables | Tracks row and cell insertion, deletion, and property changes. |
| Run formatting | Tracks formatting changes in a separate card. |
| Images | Tracks insertion and deletion. Property edits are unavailable. |
In suggesting mode, a formatting change keeps the new properties and records the
old ones, so a reviewer can put them back. A run records `w:rPrChange`, a
paragraph mark records `w:pPr/w:rPr/w:rPrChange`, and paragraph properties
record `w:pPrChange`. Accept drops the record; reject restores what it holds.
One press is one card, however many runs the selection covers. The record holds
the properties each run started with, so a second press on the same run adds no
second card. Setting a property back to the value your own record holds removes
that record.
A toggle pressed twice is not that case. Turning bold off writes an explicit off
value rather than removing the property, because the property might come from a
style. The run's properties therefore differ from the ones it started with, and
the change is recorded.
Formatting text inside your own pending insertion records nothing: the whole run
is already your proposal, so rejecting the insertion takes the words and the
formatting together. Formatting text inside another author's pending insertion
does record a change, and that record is yours. Their insertion is untouched.
If your change lands on the properties another author's record holds, their
record stays. Resolving their proposal is a review decision, so take it in the
review pane rather than through a formatting press.
Lists, indent level, and tab stops are not recorded. Changing them in suggesting
mode applies the change with no card. Table property changes made in the editor
are not recorded either; a `w:tblPrChange` a file already carries still renders.
A document that sets `w:doNotTrackFormatting` in `settings.xml` gets no
formatting records. Its text edits stay tracked.
A changed paragraph mark shows a colored pilcrow and margin change bar. This
display also works inside table cells.
One paragraph mark can contain two decisions. For example, one author can insert
a break before another author suggests its removal.
Attribution appears only in **All Markup**. Resolved views omit review colors,
pilcrows, and change bars. **No Markup** merges paragraphs when a revision
deletes their paragraph mark.
For author colors and avatars, see
[Review colors and styling](/docs/2.x/pro/review-styling).
## Add the review sidebar
` ` renders one card for each included pending decision.
Place it inside the viewport so the rail scrolls with the document.
The default sidebar excludes structural and formatting revisions. Its props can
include those revisions or filter the queue further.
| Prop | Default | Behavior |
| ------------ | ------- | --------------------------------------------------------------- |
| `filter` | None | Returns a subset of items. |
| `structural` | `false` | Adds cards for structural revisions. |
| `formatting` | `false` | Adds cards for formatting revisions. |
| `stack` | `true` | Moves overlapping cards to prevent collisions. |
| `gap` | `8` | Sets the CSS-pixel gap between stacked cards. |
| `furniture` | None | Adds host content before cards. |
| `preset` | `true` | Set `false` to keep context and anchors without packaged cards. |
Structural and formatting revisions remain marked when their cards are hidden.
Selecting one opens its balloon.
### Review parts
All listed parts accept `className` and `hidden`. The other supported props vary
by part.
| Parts | Purpose | `asChild` | `icon` |
| ------------------------------------- | ------------------------------------------ | --------- | ---------------- |
| `List` | Builds the card collection. | No | No |
| `Card` | Wraps one review item. | Yes | No |
| `Empty` | Shows the empty state. | No | No |
| `Avatar`, `Author`, `Time`, `Summary` | Display item metadata. | Yes | No |
| `Accept`, `Reject` | Resolve a revision. | Yes | Yes |
| `Resolve`, `Reopen` | Change comment thread state. | Yes | Yes |
| `Delete` | Deletes a thread or discards a suggestion. | Yes | Yes |
| `Replies` | Displays existing replies. | No | No |
| `Reply` | Adds a reply. | No | No |
| `Markers` | Shows markers while the pane is closed. | No | Function or node |
| `Balloon` | Shows formatting or structural decisions. | No | No |
| `AddComment` | Starts a comment draft. | No | No |
| `Draft` | Authors a new comment. | No | No |
`Markers` accepts an icon function because one component draws all markers. The
packaged icon identifies the item kind.
On a comment card, `Delete` removes the thread. On a tracked-change card, it
discards the suggestion. Reply delete controls remove only their reply.
The stylesheet shows delete controls on hover or keyboard focus. Cards without
a deletable target omit the control.
## Build a custom review interface
`useReview()` returns the same data and actions as the packaged rail.
React returns plain values from `useReview()`. Vue returns computed refs for
`items`, `activeKey`, `ready`, `paneOpen`, `selectionAnchorY`, and
`commentResolutionDisabledReason`. Vue templates unwrap these refs.
| Member | Behavior |
| --------------------------------- | ---------------------------------------- |
| `items` | Pending decisions in reading order |
| `activeKey`, `setActive` | Read or open an item |
| `accept`, `reject` | Resolve a revision |
| `resolve`, `reopen` | Change comment thread state |
| `reply` | Add a reply |
| `remove` | Delete a thread or discard a suggestion |
| `comment` | Comment on the current selection |
| `selectionAnchorY` | Proposed comment position, or `null` |
| `paneOpen`, `setPaneOpen` | Read or change pane state |
| `ready` | `false` before a document loads |
| `commentResolutionDisabledReason` | Engine refusal for comment state actions |
Render `item.text` as text. The document controls this value. Do not render it
as markup.
| Item field group | Fields |
| ---------------- | ------------------------------------------------- |
| Identity | `key`, `id`, `kind`, `author`, `initials`, `date` |
| Content | `text`, `replyIds` |
| Position | `anchorY`, `pageIndex` |
| State | `readOnly`, `activatable`, `isActive` |
| Revision only | `revisionKind`, `replacedText` |
The nested `item` field holds engine review data. For an `entry`, revision
ranges are in `entry.item.ranges`. Comment ranges are in `entry.item.range`.
A reply to a revision creates a comment over its range. Its
`parentRevisionId` links it to the revision. Omit comments with `parentId` or
`parentRevisionId` from top-level lists.
`readOnly` means the engine cannot resolve that item. Hide **Accept** and
**Reject** in custom cards. Packaged actions remain visible but disabled in
viewing mode.
### Activate and reveal items
`setActive` moves the caret to the item start and opens its story. It does not
select content. The item renders its own highlight.
| `reveal` value | Scroll behavior |
| -------------- | ------------------------------------------------ |
| Omitted | Centers an item only when scrolling is required. |
| `'start'` | Places the item near the viewport start. |
| `'nearest'` | Uses the minimum scroll distance. |
| `false` | Opens the item without scrolling. |
`setActive` returns `false` when activation is excluded, no range exists, or the
story cannot open.
Use `useReviewOf(editor, query)` with an existing editor. React accepts
`Editor | null`. Vue requires `Ref` and accepts a reactive query.
| Query or exclusion | Effect |
| ------------------------------- | -------------------------------------------------------------- |
| `excludeRevisionKinds` | Removes those kinds from returned `items`. |
| `placement: false` | Keeps metadata and sets placement fields to `null`. |
| `setReviewActivationExclusions` | Prevents caret-driven activation for hidden kinds. |
| `DocxEditorReview` | Sets activation exclusions from `structural` and `formatting`. |
A query filters returned data. It does not limit caret-driven activation.
## Filter by reviewer
The packaged menu exposes **Review → Markup Options → Reviewers**. The default toolbar omits
the shortcut to keep the chrome compact. A host that wants one can compose
`DocxEditor.Toolbar.Reviewers` and provide a custom `icon` (or the Vue default slot).
Each checked author keeps their tracked markup and review cards visible. Clearing an author
shows that author’s revisions as accepted: insertions remain as ordinary text, deletions leave
the layout, and formatting stays applied without markup. Comments by that author are hidden.
Reviewer visibility is view-only. It does not accept changes or alter saved DOCX content, and
showing the author again restores their markup. Bulk actions over `useReview().items` affect
only the items currently returned by the filtered review list.
Use `editor.getReviewAuthors()` to build custom chrome. Read visibility with
`editor.isReviewAuthorVisible(author)`, change one author with
`editor.setReviewAuthorVisible(author, visible)`, or call
`editor.setAllReviewAuthorsVisible(visible)` and `editor.showAllReviewAuthors()`.
## Filter tracked changes with a predicate
Use a tracked-changes predicate when reviewer names are not enough—for example, to show only
recent changes, only deletions, or a combination of author, date, kind, and document location.
The API is view-only:
```ts
import type { TrackedChangeFilterMode, TrackedChangePredicate } from '@docx-editor.dev/core/editor';
const predicate: TrackedChangePredicate = (revision) => true;
editor.setTrackedChangesFilter(predicate); // install or re-evaluate a filter
editor.setTrackedChangesFilter(predicate, 'reject'); // show excluded revisions as rejected
editor.setTrackedChangesFilter(null); // clear it
```
The predicate receives one complete `ReviewRevisionItem` for each revision decision. Return
`true` to keep the revision as tracked markup and in the review list. Return `false` to remove
its revision card and render it with the selected mode. The default mode is `accept`, matching
Word's **Show Markup → Specific People** behavior.
| Revision kind | `accept` mode | `reject` mode |
| ------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
| `insert`, `moveTo` | Proposed content remains as ordinary content. | Proposed content is omitted. |
| `delete`, `moveFrom` | Deleted content is omitted. | Deleted content returns as ordinary content. |
| `replace` | Replacement remains; replaced content is omitted. | Replacement is omitted; replaced content returns. |
| `paragraphMark`, inserted/deleted row | Accepted structure is rendered. | Original structure is rendered. |
| `format`, other structural/property | Current values remain without tracked-change markup. | Current values remain without tracked-change markup. |
### Predicate data
The predicate can use the following revision data:
| Field | Description |
| ---------------------------------------- | ---------------------------------------------------------------------------- |
| `author` | Reviewer name stored in the DOCX revision. |
| `date` | Optional raw OOXML timestamp. Validate it before date comparisons. |
| `revisionKind` | Insert, delete, replace, move, format, paragraph-mark, or structural change. |
| `text`, `replacedText` | Proposed text and, for replacements, the text being replaced. |
| `ranges` | All document ranges covered by the decision, including part names. |
| `address`, `addresses` | The OOXML revision address or addresses resolved together. |
| `nesting`, `pairedWith`, `markDirection` | Nesting, move/replacement pairing, and paragraph-mark metadata. |
| `readOnly` | Whether the engine can accept or reject this revision. |
| `id`, `replacedRangeCount`, `replyIds` | Stable decision identity and additional review-card metadata. |
Dates are document data and can be absent or invalid. Use `Date.parse` together with
`Number.isFinite` before comparing them.
### Install a filter
This example keeps only insertions from Jess on or after February 1, 2026:
#### React
```tsx
import { useEffect } from 'react';
import { useDocxEditor } from '@docx-editor.dev/react';
const CUTOFF = Date.parse('2026-02-01T00:00:00Z');
export function RecentJessInsertions() {
const editor = useDocxEditor();
useEffect(() => {
if (!editor) return;
editor.setTrackedChangesFilter((revision) => {
const timestamp = Date.parse(revision.date ?? '');
return (
revision.author === 'Jess' &&
revision.revisionKind === 'insert' &&
Number.isFinite(timestamp) &&
timestamp >= CUTOFF
);
});
return () => editor.setTrackedChangesFilter(null);
}, [editor]);
return null;
}
```
#### Vue
```vue
```
### Common recipes
Predicates are ordinary functions, so compose small rules with boolean logic:
```ts
import type { TrackedChangePredicate } from '@docx-editor.dev/core/editor';
const byJess: TrackedChangePredicate = (revision) => revision.author === 'Jess';
const deletionsOnly: TrackedChangePredicate = (revision) =>
revision.revisionKind === 'delete' || revision.revisionKind === 'moveFrom';
const inMainDocument: TrackedChangePredicate = (revision) =>
revision.ranges.some((range) => range.partName === '/word/document.xml');
editor.setTrackedChangesFilter(
(revision) => byJess(revision) && deletionsOnly(revision) && inMainDocument(revision)
);
```
### Choose how excluded revisions render
Pass `accept` to temporarily apply revisions that return `false`. Pass `reject` to render the
rejected result for content revisions, moves, paragraph marks, and inserted or deleted table rows.
Both modes are projections only: changing the mode restores the other view immediately, and saving
preserves the canonical revision markup.
```ts
const mode: TrackedChangeFilterMode = 'reject';
editor.setTrackedChangesFilter((revision) => revision.author === 'Jess', mode);
```
The mode applies only to revisions excluded by the predicate. Authors unchecked in **Review →
Markup Options → Reviewers** continue to use Word's accepted projection, even when the predicate
uses `reject`. Formatting revisions and structural property-change records do not carry a separate
layout projection today; in either mode their card and markup hide while their current values remain
painted. Use the normal reject action to restore supported formatting revisions. Read-only
structural property records cannot currently be restored by the engine.
The predicate filter and the reviewer menu compose. A revision stays tracked only when its author
is checked under **Review → Markup Options → Reviewers** and the predicate returns `true`.
The predicate runs for revisions, not comments. Comments remain visible unless the reviewer menu
hides their author.
### Update and clear a filter
The editor evaluates the predicate once per revision item when the filter or document changes and
caches the decisions for pagination. Keep it synchronous, deterministic, and free of side effects.
If the function closes over mutable state, call `setTrackedChangesFilter` again after that state
changes—even when you reuse the same function reference:
```ts
let visibleKinds = new Set(['insert', 'replace']);
const predicate: TrackedChangePredicate = (revision) => visibleKinds.has(revision.revisionKind);
editor.setTrackedChangesFilter(predicate);
visibleKinds = new Set(['delete']);
editor.setTrackedChangesFilter(predicate); // re-evaluate the captured state
editor.setTrackedChangesFilter(null); // restore all tracked markup
```
If evaluation throws, the editor preserves the last complete projection and surfaces the error.
Clear the predicate during component cleanup so a filter does not outlive the UI that owns it.
### Saving and review actions
Filtering changes layout, painted markup, review cards, and bulk actions over the currently visible
review list. The `accept` and `reject` modes do not accept or reject revisions, mutate the document,
or change saved OOXML.
Saving while a filter is active writes the same canonical revision markup as saving with no filter.
To change the document, call the normal accept or reject actions explicitly.
If `preset={false}`, use
`useStackedReviewPositions(items, heights, { gap, scale })`. Pass
`editor.getRenderScale()` as `scale`. Card heights use CSS pixels. Anchors use
document points.
## Accept or reject in bulk
The review hook has no accept-all command. Call the per-item action for each
actionable revision.
```tsx
import { DocxEditor, useEditorState } from '@docx-editor.dev/react';
import { DocxEditorReview, reviewModule, useReview } from '@docx-editor.dev/pro/react';
const MODULES = [reviewModule()];
function AcceptAll() {
const { items, accept } = useReview();
const viewing = useEditorState((state) => state.editingMode === 'viewing');
const actionable = items.filter((item) => item.kind === 'revision' && !item.readOnly);
return (
);
}
export function BulkReviewer({ bytes }: { bytes: Uint8Array }) {
return (
);
}
```
Each call resolves every location with the revision's `(id, author, date)`
tuple. The call uses one transaction and one undo step. A tracked row insertion
cannot become partly resolved.
The automation object model also provides revision collection bulk actions. See
the [editor API](/docs/2.x/editor-api).
## Detect review content without Pro
The snapshot field `hasReviewContent` detects revisions or comment anchors
without a registered review module.
```ts
const hasReview = useEditorState((state) => state.hasReviewContent ?? false);
```
Without the module, the editor renders revisions in their final state. It keeps
the original revision markup during save.
## Word compatibility
| Workflow | Behavior |
| ------------------------ | ----------------------------------------------------------------- |
| Save | Writes standard `w:ins` and `w:del` with author and date. |
| Open | Keeps Word revisions and shows them in the page and sidebar. |
| Accept or reject in Word | Resolves revisions saved by the editor. |
| Other stories | Tracks headers, footers, footnotes, and endnotes in their scopes. |
## Next steps
- [Review colors and styling](/docs/2.x/pro/review-styling): Style review content.
- [Comments](/docs/2.x/pro/comments): Add discussion threads.
- [React composition](/docs/2.x/react/composition): Arrange React review parts.
- [Vue composition](/docs/2.x/vue/composition): Arrange Vue review parts.
---
# Quickstart
Source: https://www.docx-editor.dev/docs/2.x/quickstart
This page builds a browser editor that opens a local `.docx` and downloads the edited document.
### Install
The adapter includes the string catalog and declares the engine as a peer dependency, so install both.
#### React
```bash
npm install @docx-editor.dev/react @docx-editor.dev/core
```
#### Vue
```bash
npm install @docx-editor.dev/vue @docx-editor.dev/core
```
On Next.js, Nuxt, Remix, or other SSR frameworks the editor must render client-side; mounting it during SSR throws `window is not defined`. Use the recipe in [Installation](/docs/2.x/installation). The code below works without further configuration in client-rendered apps such as Vite.
### Load, edit, save
This example puts load, edit, and save in one file. A file input supplies bytes to the editor, the editor supports editing (typing, formatting, undo, tables), and a button serializes the current state back to a `.docx` download.
The packaged `` shows a document page with loading status while a document opens.
#### React
```tsx
// App.tsx
import { useRef, useState } from 'react';
import { DocxEditor, type DocxEditorRef } from '@docx-editor.dev/react';
import '@docx-editor.dev/core/styles/editor.css';
export default function App() {
const editorRef = useRef(null);
const [file, setFile] = useState(null);
const [bytes, setBytes] = useState();
async function pick(e: React.ChangeEvent) {
const picked = e.target.files?.[0] ?? null;
setFile(picked);
setBytes(picked ? new Uint8Array(await picked.arrayBuffer()) : undefined);
}
async function download() {
const buffer = await editorRef.current?.save();
if (!buffer) return;
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file?.name ?? 'document.docx';
a.click();
URL.revokeObjectURL(url);
}
return (
{bytes && }
);
}
```
#### Vue
```vue
```
Notes for this example:
- `document` takes a `Uint8Array`, an `ArrayBuffer`, a `DocumentHandle`, or `'blank'` for an empty document. Omitting it means no document at all. The editor shows its loading screen, and every control stays disabled until you supply bytes.
- `mode` is `'edit'` (default), `'view'`, or `'suggesting'`, and is read at mount. Remount to change it.
- `save()` on the ref returns `Promise`: a complete `.docx`, or `null` when there is no document. Parsing and serialization both happen in the browser; the document stays in the browser; no server upload occurs.
- The editor fills its parent, so give the parent a non-zero CSS height. The stylesheet import is required once per app.
### Fetch instead of a file input (optional)
Load a template from your own server instead:
#### React
```ts
const bytes = new Uint8Array(await fetch('/template.docx').then((r) => r.arrayBuffer()));
//
```
#### Vue
```ts
import { useDocxSource } from '@docx-editor.dev/vue';
const { document: bytes, isLoading } = useDocxSource('/template.docx');
//
```
`useDocxSource()` fetches the resource and exposes loading state.
To react to the built-in Save action (Cmd+S, or File → Save) instead of adding your own button, handle the save event. It replaces the packaged behavior, so read the bytes from the ref:
#### React
```tsx
{
const buffer = await editorRef.current?.save();
if (buffer) await upload(buffer);
}}
/>
```
#### Vue
```vue
```
### Open a local .docx file
Run your dev server, click the file input, and pick a `.docx`, for example a document that includes tables and headers. The editor parses it client-side and the download button writes the current state back. Check the result in Microsoft Word. If something renders or saves incorrectly, [file an issue](https://github.com/eigenpal/docx-editor/issues) with the document.
If you do not have a local file, try the [live demo](https://docx-editor.dev/editor) first.
## Next steps
- [Installation](/docs/2.x/installation) for Next.js, Nuxt, Remix, and Astro specifics
- [React composition](/docs/2.x/react/composition) or [Vue composition](/docs/2.x/vue/composition) to replace the packaged chrome with your own
- [Word fidelity](/docs/2.x/word-fidelity) if you evaluate feature support and round-trip behavior
- [React props](/docs/2.x/react/props) and [Vue props](/docs/2.x/vue/props) for the full packaged-editor prop surface
---
# Composition
Source: https://www.docx-editor.dev/docs/2.x/react/composition
`` arranges public components into a packaged editor.
Use the same components to build custom chrome.
The packaged toolbar uses the hooks and primitives on this page.
Your controls can use the same command and state APIs.
## Composition requirements
| Requirement | When required | Purpose |
| ------------------------ | -------------------------------- | ---------------------------------------------------------------- |
| `DocxEditor.Root` | Every composed editor | Owns and provides the editor instance |
| `DocxEditor.Viewport` | Every composed editor | Supplies the scroll container and page-layout classes |
| `DocxEditor.Content` | Every composed editor | Mounts the painted document surface |
| Remount `key` | When you change modules | Loads the new modules |
| Positioned workspace row | When you use the navigation pane | Anchors the navigation pane |
| Pro review module | When you use Pro review chrome | Enables comments, tracked changes, and custom-node review chrome |
If you use Pro review chrome, register its modules once on `Root`. For setup,
see the [Pro package documentation](/docs/2.x/pro).
The [Igloo customization example](https://igloo.docx-editor.dev/) implements these patterns with
custom markup. Open the demo, or read the [Igloo example
source](https://github.com/eigenpal/docx-editor/tree/main/examples/igloo) and run it with `bun run
dev:igloo`.
## Primitives
Compose the three required components in this order:
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
export function Editor({ bytes }: { bytes: Uint8Array }) {
return (
);
}
```
`Root` does not render a DOM element. The rendered pages in `Content` form the
editable surface.
You can place optional chrome anywhere inside `Root`.
Optional chrome includes the toolbar, menu, rulers, navigation pane,
hyperlink popover, and context menu.
### Root props
The editor reads construction props when it creates the instance.
Changes to `document`, `fonts`, or `imageDecodePort` remount the editor.
Changes to `author`, `locale`, `mode`, `translate`, `zoom`, `zoomMode`, or the locale catalog
apply without a remount.
These changes preserve edits, caret position, and undo history.
| Prop | Type | Description |
| ----------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `document` | `DocumentSource` | Loads DOCX bytes, `'blank'`, or a `DocumentHandle`. An identity change remounts the editor. |
| `fonts` | `FontConfiguration \| FontConfigurationFragment \| FontResolver` | Supplies font bytes or a resolver. An identity change remounts the editor. |
| `author` | `string` | Sets the author for later comments, replies, and tracked changes. Changes apply without a remount. |
| `locale` | `string` | Regional date input and generated document labels. Defaults to `en-US`; updates without a remount. |
| `mode` | `'edit' \| 'view' \| 'suggesting'` | Sets the editing mode. When omitted, `w:trackRevisions` can select suggesting mode. |
| `modules` | `readonly EditorModule[]` | Registers modules during construction. Remount with a different `key` to change them. |
| `zoom` / `zoomMode` | `number` / `ZoomMode \| 'auto'` | Sets the display scale and its source. `'auto'` fits the page width. |
| `onReady` | `(editor: Editor) => void` | Runs once per instance after `Content` attaches. |
| `onChange` | `(change: DocumentChange) => void` | Reports revision and identity changes after mutations. |
| `onFontError` | `(error: EditorFontError) => void` | Reports typed font-resolution failures. |
| `translate` | `(key, params?) => string` | Resolves live document-surface labels. It defaults to the active catalog. |
| `tableInteractionLabel` | `(key) => string` | Resolves labels for table insertion controls. |
| `imageDecodePort` | `ImageDecodePort` | Overrides raster decoding for tests or custom hosts. |
For exact signatures, see the [React API reference](/docs/2.x/api/react).
### UI language and date input
`Root` reads UI translations from `LocaleProvider`; it has no `i18n` prop.
Set `locale` separately for regional date input. This example uses Polish for both:
```tsx
import { DocxEditor, LocaleProvider } from '@docx-editor.dev/react';
import { pl } from '@docx-editor.dev/i18n';
;
```
Without the provider, UI strings remain English unless an ancestor supplies a catalog.
See [internationalization](/docs/2.x/i18n) for catalog imports and defaults.
## Customization options
Use the first option that meets your requirements:
1. Override CSS custom properties from the `--doc-*` palette.
2. Use the `icon` prop to replace a glyph.
3. Use `asChild` to apply behavior to your element.
4. Override a compound slot while preserving other default slots.
5. Set `preset={false}` and arrange all parts.
6. Use [React hooks](/docs/2.x/react/hooks) with your own markup.
### Use `asChild`
`asChild` applies the part's behavior to its child.
This behavior includes handlers, disabled state, ARIA attributes, and active
state.
The part does not render a wrapper:
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import { Button } from '@/components/ui/button';
;
```
### Override a slot
A compound child replaces the slot with the same name.
Other default slots remain unchanged.
The `hidden` prop removes a slot:
```tsx
// Keep the default toolbar, replace Bold, and remove Highlight.
```
## Custom toolbar
Set `preset={false}` to remove the registry's default arrangement.
The JSX order then controls the visual order.
Packaged parts still get enabled state, active state, and commands from the
engine.
This example builds a custom toolbar from packaged parts:
```tsx
{/* Keep picker behavior and apply custom styles. */}
```
Most named parts map to one `ChromeSlotId`. `Alignment` combines the four
`alignment.*` slots. The [chrome slot reference](/docs/2.x/guides/chrome-slots)
lists every slot and named React and Vue part.
The default arrangement includes slots that the registry adds later.
A manual arrangement includes only the parts that you specify.
The `icon` prop accepts a React element.
It does not accept a component function:
```tsx
// Pass a React element.
export const MyBold = (
);
;
// Do not pass `() => `. The prop accepts a ReactNode.
```
### Add a host action
Use `Toolbar.Action` when the registry has no matching slot.
You provide the label, icon, and effect.
Use `useEditorCommand` to get enabled state for the same command.
The control can then show the engine's refusal reason.
```tsx
import {
useDocxEditor,
useEditorCommand,
useEditorState,
type EditorCommand,
} from '@docx-editor.dev/react';
const highlight = (value: string): EditorCommand => ({
type: 'setMarkAttr',
mark: 'highlight',
attr: 'val',
value,
});
function useHighlightAction() {
const editor = useDocxEditor();
const { isEnabled, disabledReason } = useEditorCommand(highlight('cyan'));
// The command supports a collapsed caret for future typing.
// Disable this action because it applies only to selected text.
const collapsed = useEditorState((s) => s.selectionCollapsed);
return {
apply: () => editor?.exec(highlight('cyan')),
enabled: isEnabled && !collapsed,
disabledReason: collapsed && isEnabled ? 'nothing is selected' : disabledReason,
};
}
function HighlightAction() {
const { apply, enabled, disabledReason } = useHighlightAction();
return (
);
}
```
Share one hook across each surface that exposes the action.
This practice keeps enabled-state rules consistent.
The chrome registry applies the same rule to packaged toolbar and menu controls.
## Custom context menu
You can combine packaged rows, custom rows, slots, and submenus.
This example also replaces icons and removes a packaged row:
```tsx
{
/* Get `editor` from `useDocxEditor()`. */
/* Get `enabled` and `apply` from `useHighlightAction()`. */
}
{/* These rows keep their packaged commands and disabled reasons. */}
{/* Remove a packaged row. */}
{/* Supply all behavior for a custom row. */}
editor?.exec({ type: 'insertBreak', kind: 'page' })}
/>
editor?.exec({ type: 'insertTable', rows: 3, cols: 3 })}
/>
{/* Get the label, icon, and enabled state from the registry. */}
;
```
The compound appends unrecognized children after its rows.
It keeps the packaged panel element, roles, keyboard behavior, and placement.
## Custom menu bar
The default menu bar derives from `CHROME_MENUS`.
Registry updates therefore appear in the default bar.
You can add a host menu or replace the **Help** menu:
```tsx
{
/* Get `editor` from `useDocxEditor()`. */
/* Get `enabled` and `apply` from `useHighlightAction()`. */
}
{/* Replace menu icons and keep registry-defined rows. */}
{/* `MenuId` accepts host-defined strings. Use `label` for host text. */}
Highlight passage
editor?.exec({ type: 'insertBreak', kind: 'page' })}
shortcut="Ctrl+Enter"
>
Page break
{/* Replace Help with links for the host application. */}
window.open('/docs', '_blank', 'noopener')}>
Documentation
;
```
## Custom navigation pane
Navigation parts are compound statics.
Add a class to each part instead of targeting internal classes.
The headings part continues to use the engine outline:
```tsx
```
## Custom loading screen
`DocxEditor.Loading` renders before document bytes arrive.
It also renders while a large document opens.
The engine reports the second state through `snapshot().isOpening`.
This example replaces the packaged loading content:
```tsx
Opening...
```
Pass `overlay` to position the loading screen over its nearest positioned
ancestor.
The opaque overlay covers the previous document while the next document opens.
The overlay does not appear when loading finishes before the delay. This
prevents a visible flash.
` ` mounts this overlay by default.
```tsx
```
Use `DocxEditor.Loading.Spinner` with `className` to style the packaged spinner.
If you conditionally mount content, use `snapshot().isLoading`.
Do not use `isOpening` for that condition.
`DocxEditor.Content` must remain mounted while the scheduled open completes.
## Custom link popover
`DocxEditor.HyperLink` provides the hyperlink popover.
It includes the URL, edit fields, apply, copy, and unlink actions.
Its parts accept `className`, `asChild`, and `hidden`.
Action parts also accept `icon`:
```tsx
```
The parts are `Url`, `Fields`, `Edit`, `Apply`, `Cancel`, `Copy`, `Unlink`,
and `Error`.
Use `useHyperlinkPopup()` when you provide all popover markup.
## Rulers
`DocxEditor.HorizontalRuler` and `DocxEditor.VerticalRuler` read page setup
and zoom from the snapshot.
They update after section and zoom changes.
A margin drag shows a preview and commits one transaction on release.
The transaction creates one undo entry.
The handles do not operate in view mode.
```tsx
{/* Position this ruler at the editing area's left edge. */}
```
## Page setup dialog
`DocxEditor.PageSetupDialog` is a controlled component.
You provide `open` and handle `onClose`.
The packaged menu connects it to **Format > Page setup**.
This example connects it to a host button:
```tsx
const [open, setOpen] = useState(false);
setOpen(false)} />
```
The dialog and `usePageSetup()` use the same engine command.
Use that hook to build a custom form.
## Content-control panel
`DocxEditor.ContentControl` inspects the content control at the caret.
Its compound parts include `Header`, `Fields`, and `Remove`.
For locks, data bindings, and fill-only mode, see the
[Content controls guide](/docs/2.x/guides/content-controls).
## Page furniture
Mount a part only when your layout needs its interface:
| Part | Interface |
| ------------------------------- | --------------------------------------------- |
| `DocxEditor.PageNumber` | Current and total page count during scrolling |
| `DocxEditor.AuthorStyle` | Review style for one author |
| `DocxEditor.ColorByChangeType` | Tracked-change colors by change type |
| `DocxEditor.FontNotice` | Rendered families without a compatible face |
| `DocxEditor.DocumentOutline` | Standalone heading list with caret navigation |
| `DocxEditor.HeaderFooterChrome` | Header and footer editing controls |
| `DocxEditor.NotesChrome` | Footnote and endnote controls |
For review colors, see
[Tracked changes](/docs/2.x/pro/tracked-changes).
## Custom labels
Composed chrome resolves labels through the active locale catalog.
`LocaleProvider` supplies labels to `DocxEditor.Toolbar`, `Menu`,
`ContextMenu`, and other parts.
Pass `t` when you need to rename labels.
`useChromeTranslate(overrides?)` returns a catalog-backed resolver.
It checks your overrides before the catalog:
```tsx
import { DocxEditor, useChromeTranslate } from '@docx-editor.dev/react';
// Keep this Map at module scope to preserve its identity.
// A Map also avoids inherited object keys.
const OVERRIDES = new Map([
['contextMenu.cut', 'Cut text'],
['formattingBar.bold', 'Heavy'],
]);
function MyToolbar() {
const t = useChromeTranslate(OVERRIDES);
return ;
}
```
Keys without overrides resolve from the catalog.
## Custom colors
The editor chrome uses the `--doc-*` custom-property palette.
Override these properties in a scope to theme its toolbar, menu, panels,
pickers, rulers, and navigation pane:
```css
.my-nav {
--doc-surface: transparent;
--doc-text: #fff;
--doc-border: rgba(255, 255, 255, 0.25);
}
```
Follow these styling requirements:
- Do not target `docx-*` classes.
These classes are implementation details.
- Do not use `!important`.
Use a component prop, a `--doc-*` property, or an element that you own.
- Do not theme the document canvas.
The canvas preserves the document's Word-compatible appearance.
## Custom tracked changes and comments
[`@docx-editor.dev/pro`](/docs/2.x/pro) provides the review surface.
`DocxEditorReview` renders one card for each pending decision.
Its props select decisions and control stacking.
Its parts control card markup.
```tsx
import { DocxEditorReview } from '@docx-editor.dev/pro/react';
item.kind === 'comment'}
// Keep structural and formatting changes on the document page.
structural={false}
formatting={false}
stack
gap={12}
furniture={ }
/>;
```
The `furniture` prop renders host content above the cards.
Use it for controls such as filters or a legend.
Place the compound inside the viewport to scroll it with the document.
For suggesting mode, bulk actions, and `useReview()`, see
[Tracked changes](/docs/2.x/pro/tracked-changes).
### Custom cards
Each card is a compound component.
You can reorder, hide, or replace icons for its parts:
```tsx
Nothing to review
```
Each part accepts `className`, `asChild`, and `hidden`.
Action parts also accept `icon`.
Use a render callback on `List` when you replace all packaged card markup.
Root parts remain siblings.
This structure supports a custom **Add comment** control and custom cards:
```tsx
{(item) => }
```
The root callback form remains an alias for an implicit `List`.
It cannot include root siblings.
Use `List` for new code.
Set `preset={false}` when you replace the packaged review arrangement.
The component still provides rail context and root-owned positioning.
It does not render omitted defaults.
`useStackedReviewPositions(items, heights, options)` provides the packaged
stacking calculation.
Pass `{ gap, scale: editor.getRenderScale() }` as `options`.
Use `useReview()` when you need the review queue without chrome.
For placement details, see [Tracked changes](/docs/2.x/pro/tracked-changes).
Use `useReviewItem()` to read the card that contains a child:
```tsx
import { DocxEditorReview, useReviewItem } from '@docx-editor.dev/pro/react';
function OpenSource() {
const item = useReviewItem();
// Show this action only for custom-node review cards.
if (item?.kind !== 'custom') return null;
return ;
}
;
```
## Custom nodes
`CustomNodeChrome` renders interaction markers for
[custom nodes](/docs/2.x/pro/custom-nodes).
The node definition's `chrome.color` sets each marker color.
Use click and hover handlers to update host state.
```tsx
import { CustomNodeChrome, CustomNodeContextMenu } from '@docx-editor.dev/pro/react';
setPopover({ at: node.rect, attrs: node.attrs })}
onNodeHover={(node) => prefetch(node.attrs['sourceId'])}
/>
{/* Add an edit row when the context-menu target is a custom node. */}
openEditForm(node)} />
;
```
An activated node includes `name`, `attrs`, `tag`, and `rect`.
The viewport-relative `rect` can anchor a host popover.
The node also includes `nodeId` and `text` when available.
Use `useCustomNodeDefinitions()` to read registered definitions.
Register the required module on `Root` before you use either compound.
## A full composition
This example combines common composition parts:
```tsx
export function Workspace({ bytes }: { bytes: Uint8Array }) {
return (
{/* Mount optional overlays that this application uses. */}
);
}
```
Composition lets you mount each part explicitly.
Omitted parts do not render an interface.
The packaged `` mounts the default part set.
## Layout constraints
The navigation pane requires a positioning context.
Place it beside the viewport in a row with `position: relative`.
The pane uses absolute positioning over the document gutter.
Without that context, it enters normal flow and moves the page.
Do not set `z-index` on the workspace row or viewport.
That property can create a stacking context around the context menu.
A fixed panel cannot escape its containing stacking context.
The panel can then render under the chrome bar.
## Keep the caret
A `mousedown` event on the document moves the caret.
Prevent the event on chrome that must preserve the document caret:
```tsx
e.preventDefault()}>{/* your toolbar */}
```
Do not prevent `mousedown` on `input`, `select`, or `textarea` elements.
These controls require focus.
Packaged chrome applies this behavior.
Apply the same behavior to custom chrome.
## Next steps
- [Igloo customization example](https://igloo.docx-editor.dev/)
- [Igloo example source](https://github.com/eigenpal/docx-editor/tree/main/examples/igloo)
- [Custom nodes example](https://github.com/eigenpal/docx-editor/tree/main/examples/custom-nodes)
- [React hooks](/docs/2.x/react/hooks)
- [Toolbar guide](/docs/2.x/guides/toolbar)
- [React props](/docs/2.x/react/props)
---
# React examples
Source: https://www.docx-editor.dev/docs/2.x/react/examples
These examples import public APIs from package roots.
## Load DOCX bytes
This component fetches a DOCX file and passes its bytes to the editor:
```tsx
import { useEffect, useState } from 'react';
import { DocxEditor } from '@docx-editor.dev/react';
export function Editor({ url }: { url: string }) {
const [doc, setDoc] = useState();
useEffect(() => {
let cancelled = false;
fetch(url)
.then((response) => response.arrayBuffer())
.then((buffer) => {
if (!cancelled) setDoc(new Uint8Array(buffer));
});
return () => {
cancelled = true;
};
}, [url]);
return ;
}
```
## Save through the ref
This component calls `DocxEditorRef.save()` and uploads the result:
```tsx
import { useRef } from 'react';
import { DocxEditor, type DocxEditorRef } from '@docx-editor.dev/react';
export function SaveButton({ bytes }: { bytes: Uint8Array }) {
const ref = useRef(null);
async function save() {
const next = await ref.current?.save();
if (!next) return;
await fetch('/api/documents/42', { method: 'PUT', body: next });
}
return (
<>
>
);
}
```
## Compose custom chrome
This example replaces the packaged frame with composition primitives.
The custom **Bold** button gets its state from `useEditorCommand`:
```tsx
import { DocxEditor, useEditorCommand } from '@docx-editor.dev/react';
function BoldButton() {
const bold = useEditorCommand('text.bold');
return (
);
}
export function CustomChrome({ bytes }: { bytes: Uint8Array }) {
return (
);
}
```
## Automate an open editor
This component uses the Editing API to read the first paragraph.
It disposes the browser runtime after the operation:
```tsx
import { useDocxEditor } from '@docx-editor.dev/react';
import { DocxEditor as BrowserDocxEditor } from '@docx-editor.dev/editor-api/browser';
export function AutomateButton() {
const editor = useDocxEditor();
async function run() {
if (!editor) return;
const runtime = BrowserDocxEditor.createBrowser(editor);
try {
await runtime.run(async (context) => {
const first = context.document.body.paragraphs.getFirst();
first.load('text');
await context.sync();
});
} finally {
runtime.dispose();
}
}
return ;
}
```
## Next steps
- [React package overview](/docs/2.x/react)
- [React props](/docs/2.x/react/props)
- [Toolbar guide](/docs/2.x/guides/toolbar)
- [Editing API documentation](/docs/2.x/editor-api)
---
# Hooks
Source: https://www.docx-editor.dev/docs/2.x/react/hooks
Call these hooks inside ``.
You can also call them inside ``, which renders that root.
The hooks read the editor from React context.
The packaged toolbar, menu, and navigation pane use the same hooks.
## `useEditorCommand`
Pass a chrome slot ID to `useEditorCommand`. The
[chrome slot reference](/docs/2.x/guides/chrome-slots) lists every available
ID and its named toolbar part.
The hook returns the state and action for a control:
```tsx
import { useEditorCommand } from '@docx-editor.dev/react';
function BoldButton() {
const bold = useEditorCommand('text.bold');
return (
);
}
```
| Field | Type | Description |
| ---------------- | ---------------- | ------------------------------------------------ |
| `execute()` | `() => boolean` | Runs the command and reports whether it applied. |
| `isActive` | `boolean` | Reports the command state at the caret. |
| `isEnabled` | `boolean` | Reports whether the command can run. |
| `disabledReason` | `string \| null` | Explains why the command cannot run. |
Use `isEnabled` as the enabled-state source.
Use `disabledReason` when you explain a disabled control.
Pass an `EditorCommand` when no chrome slot matches your action:
```tsx
const suggest = useEditorCommand({ type: 'setEditingMode', mode: 'suggesting' });
```
## `useEditorState`
Use `useEditorState` to subscribe to part of the editor snapshot.
The hook runs the selector for each state update.
It renders your component only when the selected value changes:
```tsx
import { useEditorState } from '@docx-editor.dev/react';
function PageIndicator() {
const page = useEditorState((s) => s.page);
return (
{page.current} / {page.total}
);
}
function SaveButton() {
const dirty = useEditorState((s) => s.canUndo ?? false);
return ;
}
```
Pass a comparison function as the second argument for object values:
```tsx
const formatting = useEditorState(
(s) => s.formatting,
(a, b) => a?.bold === b?.bold && a?.italic === b?.italic
);
```
Select only the state that your component needs.
A page indicator does not need to render after a bold-state change.
Useful fields include:
- `page`
- `selection` and `selectionCollapsed`
- `formatting`
- `table` and `image`
- `editable`
- `isLoading` and `isOpening`
- `parseError`
- `editingMode`
- `canUndo` and `canRedo`
- `pageSetup`
- `fontSubstitutions`
- `hasReviewContent`
- `lastRejection`
## `useDocxEditor`
`useDocxEditor` returns the editor instance.
It returns `null` before the `DocxEditor.Root` mount effect creates the
instance.
It also returns `null` outside a `DocxEditor.Root`.
Use the instance for actions and one-time reads:
```tsx
import { useDocxEditor } from '@docx-editor.dev/react';
function SaveButton() {
const editor = useDocxEditor();
return (
);
}
```
Calling `editor.snapshot()` during render does not subscribe your component.
Use `useEditorState` for reactive state.
## `useEditorEvent`
Use `useEditorEvent` to subscribe for the component's lifetime:
```tsx
import { useEditorEvent } from '@docx-editor.dev/react';
useEditorEvent('selectionChange', () => setPanelOpen(false));
useEditorEvent('change', (change) => void autosave(change.revision));
```
## `useFontFamily`
`useFontFamily` provides font-picker state.
It returns the current value, options, setter, and enabled state:
```tsx
import { useFontFamily } from '@docx-editor.dev/react';
function FontPicker() {
const font = useFontFamily();
return (
);
}
```
`useParagraphStyle` returns the same shape for paragraph styles.
## `usePageSetup`
Use `usePageSetup` to read and change the current section.
The hook supports margins, orientation, and paper size:
```tsx
import { usePageSetup } from '@docx-editor.dev/react';
function OrientationToggle() {
const { pageSetup, apply, isEnabled } = usePageSetup();
const landscape = pageSetup?.orientation === 'landscape';
return (
);
}
```
## `useParagraphFormat`
Use `useParagraphFormat` to read and change the paragraph at the selection.
`apply` sends the supplied fields as one command, so one call creates one undo
step:
```tsx
import { useParagraphFormat } from '@docx-editor.dev/react';
function DoubleSpaceButton() {
const { format, apply, isEnabled } = useParagraphFormat();
const isDouble = format?.lineSpacing?.value === 2;
return (
);
}
```
A field is `null` when selected paragraphs have different values. A checkbox
shows an indeterminate state. A number field has no indeterminate state. It shows
a default until you change it. Omitted fields are not written, so existing values
stay. Spacing, line-spacing, and indent fields also accept `null`. That value
clears the local setting so the style supplies it. Writing `0` sets an explicit
value instead.
For the whole form, `DocxEditor.ParagraphDialog` is the Paragraph dialog
over this hook.
## `useDocumentOutline`
`useDocumentOutline` returns headings in document order.
It also provides an action that moves to a heading:
```tsx
import { useDocumentOutline } from '@docx-editor.dev/react';
function Outline() {
const { items, selectedBlockId, goTo, isEmpty } = useDocumentOutline();
if (isEmpty) return No headings
;
return (
{items.map(({ heading, depth }) => (
-
))}
);
}
```
Each item has the shape `{ heading, depth }`.
`heading` has the shape `{ text, level, blockId }`.
`depth` measures indentation from the shallowest heading in the document.
This calculation aligns a top-level Heading 2 with the base.
Use `headings` when you need the flat list without calculated indentation.
## `useDocumentSearch`
`useDocumentSearch` provides delayed search and match navigation:
```tsx
import { useDocumentSearch } from '@docx-editor.dev/react';
function Find() {
const search = useDocumentSearch();
return (
<>
search.setQuery(event.target.value)} />
{search.matches.length === 0 ? 0 : search.activeIndex + 1}
{' / '}
{search.matches.length}
{search.truncated && '+'}
>
);
}
```
The result also includes these pairs:
- `matchCase` and `setMatchCase`
- `wholeWord` and `setWholeWord`
## Other hooks
| Hook | Return value |
| --------------------------------- | ------------------------------------------------------------------------------------------------- |
| `useDocxSource(source, options?)` | Fetches bytes and fonts for a URL, `File`, or `Blob`. It supports cancellation. |
| `useEditorValueCommand(slotId)` | Provides state for value commands such as `'image.wrap'` and `'image.altText'`. |
| `useParagraphIndent()` | Provides current indents and an `apply` action. |
| `useHyperlinkPopup()` | Provides state for a custom hyperlink panel. |
| `useContentControl()` | Provides content-control locks, value writes, and form-fill state. |
| `useHeaderFooterState()` | Returns the active header or footer scope, or `null`. |
| `useNoteScopeState()` | Returns the active footnote or endnote scope. |
| `useContextMenuTarget()` | Returns the element that received the last context-menu action. |
| `useNavigationPane(options?)` | Provides navigation-pane open state and width. |
| `useTranslation()` | Returns `{ t }` for the active locale catalog. |
| `useChromeTranslate(overrides?)` | Returns a catalog resolver that checks an override `Map` first. |
| `useFonts(source, ...fragments)` | Builds a stable `FontResolver`. See the [Fonts guide](/docs/2.x/guides/fonts). |
| `useNotePropertiesState()` | Provides note-numbering properties for the current scope. |
| `useEditorSnapshot(editor)` | Returns a revision counter for `useSyncExternalStore`. |
| `useNavigationShift()` | Returns the horizontal offset for an open navigation pane. |
| `useReviewGutter()` | Returns the inline reservations for the active review rail. |
| `useTableBorderTargetLabel()` | Returns the active table-border target label. |
| `useReviewAuthors()` | Returns tracked-change authors, then comment-only authors. Each item includes its resolved style. |
| `useEditorCaret()` | Returns `{ paragraphId, offset }` for APIs that accept an `at` position. |
| `useZoom()` | Reads and sets zoom from custom chrome. |
| `useToolbarContext()` | Returns toolbar compound context for custom slot parts. |
| `useToolbarLabel()` | Returns the active toolbar-scope label. |
| `useToolbarLabelFor(slotId)` | Resolves the label for one slot ID. |
| `useScopeClassName()` | Returns the scoped chrome class prefix. |
| `useScopedChromeAnchor()` | Returns anchor metadata for scoped overlay chrome. |
Use `useContentControlInstance()` outside the owning content-control part.
Use `useHyperlinkPopupInstance()` outside the owning hyperlink part.
These hooks provide context-free variants of the corresponding hooks.
[`@docx-editor.dev/pro/react`](/docs/2.x/pro) provides `useReview`,
`useReviewOf`, `useReviewItem`, and `useReviewAuthor`.
[`@docx-editor.dev/pro/vue`](/docs/2.x/pro) provides the Vue equivalents.
## Next steps
- [React composition](/docs/2.x/react/composition)
- [Chrome slot reference](/docs/2.x/guides/chrome-slots)
- [React API reference](/docs/2.x/api/react)
---
# 2.x/react/index
Source: https://www.docx-editor.dev/docs/2.x/react/index
## Install
Install the React adapter and its peer dependency:
```bash
npm install @docx-editor.dev/react @docx-editor.dev/core
```
The adapter uses `@docx-editor.dev/core` as a peer dependency.
The package includes the string catalog.
Install [`@docx-editor.dev/pro`](/docs/2.x/pro) for tracked changes,
comments, and custom nodes.
Install [`@docx-editor.dev/editor-api`](/docs/2.x/editor-api) to automate
documents from code.
## Quickstart
Render `` with DOCX bytes:
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
export default function App() {
return ;
}
```
`` renders the title bar, menu, toolbar, navigation pane,
hyperlink popover, context menu, and editable document.
With the Next.js App Router, render the editor inside a `"use client"`
component.
The editor requires browser APIs and does not support server rendering.
## Package exports
- `DocxEditor` provides the packaged editor and compound parts.
Parts include `DocxEditor.Root`, `DocxEditor.Viewport`,
`DocxEditor.Content`, `DocxEditor.Toolbar`, `DocxEditor.Menu`,
`DocxEditor.Navigation`, `DocxEditor.HyperLink`, and
`DocxEditor.ContextMenu`.
- Hooks include `useDocxEditor`, `useEditorState`, `useEditorCommand`,
`useEditorEvent`, `usePageSetup`, `useParagraphIndent`, and
`useFontFamily`.
- Top-level components include `DocxEditorRoot`, `DocxEditorViewport`,
`DocxEditorContent`, `DocxEditorToolbar`, `DocxEditorMenu`,
`DocxEditorNavigation`, and `DocxEditorPageSetupDialog`.
[`@docx-editor.dev/pro`](/docs/2.x/pro) provides the licensed review module
and sidebar.
The React adapter does not export these features.
Import components, hooks, and engine helpers from the package root.
The package does not provide `/ui`, `/hooks`, or `/dialogs` subpaths.
## Provider primitives
Use the provider primitives when you need custom chrome:
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
export function CustomFrame({ bytes }: { bytes: Uint8Array }) {
return (
);
}
```
## Toolbar
Use `DocxEditorToolbar` or its `DocxEditor.Toolbar` compound alias.
This example adds a custom **Bold** button:
```tsx
import { DocxEditor, useEditorCommand } from '@docx-editor.dev/react';
function BoldButton() {
const bold = useEditorCommand('text.bold');
return (
);
}
export function ToolbarOnly({ bytes }: { bytes: Uint8Array }) {
return (
);
}
```
Import `useEditorCommand`, `useEditorState`, and `useFontFamily` from the
package root.
## Editing API
Use `@docx-editor.dev/editor-api/browser` to control the open document.
Pass the React adapter's editor instance to the Office.js-compatible API:
```tsx
import { useDocxEditor } from '@docx-editor.dev/react';
import { DocxEditor as BrowserDocxEditor } from '@docx-editor.dev/editor-api/browser';
function AutomationButton() {
const editor = useDocxEditor();
function run() {
if (!editor) return;
const runtime = BrowserDocxEditor.createBrowser(editor);
// Use `runtime`, then call `runtime.dispose()`.
}
return (
);
}
```
For object model details, see the
[Editing API documentation](/docs/2.x/editor-api).
## Next steps
- [React composition](/docs/2.x/react/composition)
- [React hooks](/docs/2.x/react/hooks)
- [React props](/docs/2.x/react/props)
- [React examples](/docs/2.x/react/examples)
- [Igloo customization example](https://igloo.docx-editor.dev/)
- [Igloo example source](https://github.com/eigenpal/docx-editor/tree/main/examples/igloo)
- [React API reference](/docs/2.x/api/react)
---
# Props
Source: https://www.docx-editor.dev/docs/2.x/react/props
`` provides a packaged host for the provider primitives.
This page groups its props by task.
For all signatures, see the [React API reference](/docs/2.x/api/react).
## Document and mount state
Use `document` for the document source.
Use `fonts` to supply font metrics for measurement.
| Prop | Type | Description |
| ---------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `document` | `DocumentSource` | DOCX bytes, `'blank'`, or an existing `DocumentHandle`. |
| `fonts` | `FontConfiguration \| FontConfigurationFragment \| FontResolver` | Font bytes or a resolver for text shaping and pagination. |
| `author` | `string` | Author for later comments, replies, and tracked changes. Changes apply without a remount. |
| `locale` | `string` | BCP-47 locale for regional date input and generated labels. Defaults to `en-US`; updates without a remount. |
| `mode` | `'edit' \| 'view' \| 'suggesting'` | Editing mode. Changes apply without a remount. |
| `zoom` | `number` | Fixed display scale. Changes apply without a remount. |
| `zoomMode` | `ZoomMode \| 'auto'` | Scale source. The default `'auto'` fits the page width. |
The editor colors tracked changes by author.
Changing `author` preserves the editor instance and all existing revisions.
Mount ` ` to override one author's style.
Mount ` ` to color changes by type.
These settings do not have equivalent props.
For details, see [Tracked changes](/docs/2.x/pro/tracked-changes).
This example loads DOCX bytes in editing mode:
```tsx
const response = await fetch('/template.docx');
const bytes = new Uint8Array(await response.arrayBuffer());
;
```
For regional date input, pass `locale="en-GB"` for day/month input or
`locale="pl-PL"` for Polish dates such as `01.02.2030`. The same prop works on
`DocxEditor.Root`. It preserves dates already in the document. Use `i18n` separately
to customize UI strings; see [date input behavior](/docs/2.x/guides/fields) for details.
## Modules
The `modules` prop registers capability modules during construction.
[`@docx-editor.dev/pro`](/docs/2.x/pro) uses modules for tracked changes,
comments, and custom nodes.
| Prop | Type | Description |
| --------- | ------------------------- | ------------------------------------------------------------- |
| `modules` | `readonly EditorModule[]` | Capability modules that the editor loads during construction. |
The editor reads `modules` only during construction.
A later array change has no effect.
To change modules, remount the editor with a different React `key`:
```tsx
import { DocxEditor } from '@docx-editor.dev/react';
import { reviewModule } from '@docx-editor.dev/pro/react';
const MODULES = [reviewModule()];
;
```
Without a module, the editor preserves revisions and comments during save.
It renders their final document state.
Register the review module to show and manage them.
For details, see the [Pro package documentation](/docs/2.x/pro).
## Chrome and layout
These props control the packaged frame around the document.
| Prop | Type | Description |
| -------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------- |
| `chrome` | `boolean` | Shows the packaged title bar and toolbar. Set `false` for the document surface. |
| `title` | `string` | Sets the title-bar document name. |
| `onTitleChange` | `(title) => void` | Enables title editing and receives each new title. |
| `renderTitleBarLeft` / `renderTitleBarRight` | `() => ReactNode` | Renders host content in title-bar slots. |
| `colorMode` | `'light' \| 'dark' \| 'system'` | Sets the chrome theme. `'system'` follows the operating system. |
| `menu` | `boolean \| DocxEditorMenuProps` | Shows, hides, or configures the packaged menu bar. |
| `navigation` | `boolean` | Shows or hides the packaged navigation pane. |
| `rulers` | `boolean` | Shows or hides the horizontal and vertical rulers. |
| `hyperlinkPopup` | `boolean` | Shows or hides the packaged hyperlink popover. |
| `contextMenu` | `boolean \| DocxEditorContextMenuProps` | Shows, hides, or configures the packaged context menu. |
| `children` | `DocxEditorChildren` | Renders extra chrome inside the viewport after the document pages. |
| `t` | `(key, params?) => string` | Resolves live chrome and drawing labels. |
| `i18n` | `Translations` | Sets live chrome and drawing labels for this editor. |
## Dark mode
`colorMode` changes the editor chrome and applies a display transform to the document canvas.
The setting does not change authored document colors or saved output.
Printing uses a light page regardless of this setting.
This example controls the editor color mode:
```tsx
const [colorMode, setColorMode] = useState<'light' | 'dark'>('light');
```
For theme behavior, see the [Dark mode guide](/docs/2.x/guides/dark-mode).
## Fonts
`fonts` supplies font bytes for text measurement.
Matching metrics produce line wraps and page breaks that match Word more closely.
The editor loads supported embedded fonts without extra configuration.
The font picker combines declared document fonts with configured fonts.
Use `useFontFamily()` to read that list.
| Prop | Type | Default | Description |
| ------------- | ---------------------------------------------------------------- | ------- | ----------------------------------------------------------------------- |
| `fonts` | `FontConfiguration \| FontConfigurationFragment \| FontResolver` | None | Font bytes or a resolver called for each load. |
| `onFontError` | `(error: EditorFontError) => void` | None | Reports failures such as corrupt data, HTTP errors, or hash mismatches. |
Use `packagedFonts()` for the Word default substitutes.
The editor calls it once per load with the families that document declares.
It loads a family when the document names it, or when that family is the
document's default face, so a document pays for what it declares instead of all
20 eager faces. Nothing is fetched from a third party.
The default face counts because a run that names no font still has to be measured
in one. That face is Calibri, so Carlito loads for every document.
Wrap it in `useFonts` to keep one resolver identity:
```tsx
import { DocxEditor, useFonts } from '@docx-editor.dev/react';
import { packagedFonts } from '@docx-editor.dev/fonts';
function Editor({ bytes }: { bytes: Uint8Array }) {
const fonts = useFonts(packagedFonts());
return report(error.code)} />;
}
```
Add an origin by adding an argument. Arguments compose first-wins:
```tsx
import { googleFonts } from '@docx-editor.dev/fonts/google';
const fonts = useFonts(packagedFonts(), googleFonts());
```
The editor samples `fonts` at mount.
Changing its identity remounts the editor, which is why an inline resolver needs
`useFonts`.
`packagedFonts()` resolves after the document is parsed, so the first layout uses
fixed measurement and the editor re-paginates when the faces arrive. Edits made in
between survive that; the undo history behind them does not. For a document that
must paginate correctly on the first pass, use `defaultFonts()` instead. For more
information, see [Fonts and measurement](/docs/2.x/guides/fonts#choose-between-lazy-and-eager-loading).
A resolver can make network requests while the editor opens a document.
The editor does not fetch external fonts without a configured resolver.
For font sources and `loadFonts`, see the
[Fonts and measurement guide](/docs/2.x/guides/fonts).
## Title bar customization
Use these props to customize the title bar.
| Prop | Type | Description |
| --------------------- | ----------------- | ------------------------------- |
| `title` | `string` | Display name in the title bar. |
| `onTitleChange` | `(name) => void` | Enables in-place title editing. |
| `renderTitleBarLeft` | `() => ReactNode` | Left-side title bar slot. |
| `renderTitleBarRight` | `() => ReactNode` | Right-side title bar slot. |
```tsx
updateMetadata({ name })}
renderTitleBarRight={() => }
/>
```
## Callbacks
Use callbacks to connect the editor to your application.
| Prop | Type | Description |
| ------------- | ---------------------------------- | ----------------------------------------------------------------------------------- |
| `onReady` | `(editor: Editor) => void` | Runs after the editor and document surface mount. |
| `onChange` | `(change: DocumentChange) => void` | Runs after mutations with revision and identity changes. It does not receive bytes. |
| `onSave` | `() => void` | Overrides the packaged **File > Save** action. |
| `onOpen` | `() => void` | Overrides the packaged **File > Open** action. |
| `onFontError` | `(error: EditorFontError) => void` | Reports typed font-resolution failures. |
```tsx
console.log(editor.snapshot())}
onChange={(change) => reportRevision(change.revision)}
onSave={() => void persist()}
onOpen={() => void openPicker()}
onFontError={(err) => reportError(err)}
/>
```
## Ref methods
Use `DocxEditorRef` to load, save, focus, or access the `Editor` facade.
`` exposes this handle through `forwardRef`.
This example saves and accesses the editor:
```tsx
import { useRef } from 'react';
import { DocxEditor, type DocxEditorRef } from '@docx-editor.dev/react';
const editorRef = useRef(null);
const buffer = await editorRef.current?.save();
const editor = editorRef.current?.getEditor();
editorRef.current?.focus();
```
The ref has seven methods:
| Method | Returns | What it does |
| ------------------------- | ------------------------------ | ----------------------------------------------------------------------------------- |
| `save()` | `Promise` | Serialize the current document to a `.docx` buffer. `null` if there is no document. |
| `load(document)` | `void` | Load DOCX bytes, `'blank'`, or an existing `DocumentHandle`. |
| `getDocumentHandle()` | `DocumentHandle \| null` | Gets the current document identity and revision. |
| `getEditor()` | `Editor \| null` | Gets the full editor facade. |
| `exec(command, options?)` | `ExecResult` | Run a typed command against the current scope. |
| `snapshot(options?)` | `EditorSnapshot` | Read the current facade snapshot. |
| `focus()` | `void` | Focus the mounted editor surface. |
For exact signatures, see the
[React API reference](/docs/2.x/api/react).
## Next steps
- [React package overview](/docs/2.x/react)
- [React examples](/docs/2.x/react/examples)
- [React API reference](/docs/2.x/api/react)
---
# Composables
Source: https://www.docx-editor.dev/docs/2.x/vue/composables
Call each composable during `setup()` inside `DocxEditor.Root`. The packaged
`` also renders this root.
These composables return a ref, or a plain object that holds refs. A template
unwraps a ref only when the ref is a top-level binding. A ref that is a property
of a returned object stays a ref, so read it with `.value` in the template and in
script code.
## useEditorCommand
Pass a chrome slot ID to get its action and reactive command state. The
[chrome slot reference](/docs/2.x/guides/chrome-slots) lists every available
ID and its named toolbar part:
```vue
```
`execute()` returns `true` when the editor applies the command. `isActive`,
`isEnabled`, and `disabledReason` are computed refs on a plain object. The
template does not unwrap them, so read each one with `.value`.
The engine is the only source for enabled state. Show `disabledReason` when a
control cannot run.
You can also pass a reactive slot ID or an `EditorCommand`:
```ts
import { computed } from 'vue';
import { useEditorCommand } from '@docx-editor.dev/vue';
const mode = computed(() => ({
type: 'setEditingMode' as const,
mode: 'suggesting' as const,
}));
const suggest = useEditorCommand(mode);
```
## useEditorState
Subscribe to one slice of the editor snapshot. The composable returns a
read-only shallow ref.
```vue
{{ page.current }} / {{ page.total }}
```
The selector runs after editor updates. The ref changes only when the selected
value changes.
Pass a comparator when you select an object:
```ts
import { useEditorState } from '@docx-editor.dev/vue';
const formatting = useEditorState(
(snapshot) => snapshot.formatting,
(left, right) => left?.bold === right?.bold && left?.italic === right?.italic
);
```
Select the smallest useful value. A page selector then stays stable when the
user changes bold formatting.
Useful snapshot fields include `page`, `selection`, `selectionCollapsed`,
`formatting`, `table`, `image`, `editable`, `isLoading`, `isOpening`,
`parseError`, `editingMode`, `canUndo`, `canRedo`, `pageSetup`,
`fontSubstitutions`, `hasReviewContent`, and `lastRejection`.
## useDocxEditor
This composable returns a shallow ref to the editor instance. Its value is
`null` before the `DocxEditor.Root` mount effect creates the instance, and
outside a `DocxEditor.Root`.
```vue
```
Use the instance for actions and one-time reads. A direct `snapshot()` call
does not create a Vue subscription. Use `useEditorState` for reactive state.
## useEditorEvent
Subscribe to an editor event for the component lifetime:
```ts
import { useEditorEvent } from '@docx-editor.dev/vue';
useEditorEvent('selectionChange', () => {
panelOpen.value = false;
});
useEditorEvent('change', (change) => {
void autosave(change.revision);
});
```
## useEditorCaret
Returns the caret as `{ paragraphId, offset }`. Write APIs accept this shape as
their `at` value.
## useEditorSnapshot
Returns a revision ref for an editor. Use it for a custom external-store
subscription.
## useZoom
Returns the current zoom and controls that change it.
## useFontFamily
Returns the current family, available families, setter, and enabled state:
```vue
```
The current family is on the `value` member, and that member is a computed
ref. Read it as `font.value.value`.
`useParagraphStyle` provides the same pattern for paragraph styles.
## usePageSetup
Read and change page size, margins, orientation, and scope:
```vue
```
`apply()` returns `true` when the editor applies the update. Sizes and margins
use twips. Set `scope` to `'section'` or `'document'`.
## useParagraphFormat
Use `useParagraphFormat` to read and change the paragraph at the selection.
`apply` sends the supplied fields as one command, so one call creates one undo
step. Read the returned refs with `.value`:
```vue
```
A field is `null` when selected paragraphs have different values. A checkbox
shows an indeterminate state. A number field has no indeterminate state. It shows
a default until you change it. Omitted fields are not written, so existing values
stay. Spacing, line-spacing, and indent fields also accept `null`. That value
clears the local setting so the style supplies it. Writing `0` sets an explicit
value instead.
For the whole form, `DocxEditorParagraphDialog` is the Paragraph dialog over
this composable.
## useParagraphIndent
Returns current paragraph indents and an `apply()` action for ruler controls.
## useDocumentOutline
Read headings in document order and move the selection to a heading:
```vue
No headings
-
```
Each item has `{ heading, depth }`. A heading has `text`, `level`, and
`blockId`. `depth` starts at zero for the shallowest heading in the document.
Use `headings` when you do not need the calculated depth.
## useDocumentSearch
Search the document and move between matches:
```vue
{{ search.matches.value.length === 0 ? 0 : search.activeIndex.value + 1 }}
/ {{ search.matches.value.length }}{{ search.truncated.value ? '+' : '' }}
```
The composable waits 150 milliseconds before it searches. `isPending` reports
that delay. It also provides `goTo`, `clear`, `matchCase`, `setMatchCase`,
`wholeWord`, and `setWholeWord`. `truncated` becomes true at the 2,000-match
limit.
## useNavigationPane
Returns the navigation pane state, width, active tab, and setters.
## useNavigationShift
Returns the horizontal page offset caused by the open navigation pane.
## useReviewGutter
Returns the inline reservations for the active review rail.
The full card column changes to compact marker strips on narrow viewports.
## useDocxSource
Fetch bytes and fonts from a URL string, a `URL`, a `Uint8Array`, or an
`ArrayBuffer`:
```ts
const { document, fonts, error, isLoading } = useDocxSource('/sample.docx', {
fonts: packagedFonts(),
});
```
`fonts` takes one origin or a list of them in precedence order, such as
`[packagedFonts(), googleFonts()]`. With an on-demand resolver, `document` is
released as soon as the bytes arrive. With an eager loader such as
`defaultFonts`, it waits for the fonts first.
## useFonts
Builds one `FontResolver` from any number of font origins, first-wins in
argument order. See the [font guide](/docs/2.x/guides/fonts).
## useHyperlinkPopup and useHyperlinkPopupInstance
Returns the link popover state and actions. Use
`useHyperlinkPopupInstance` outside the component that owns the popover
context.
## useContentControl and useContentControlInstance
Returns content-control locks, value actions, and form-fill state. Use
`useContentControlInstance` outside the component that owns this context.
## useHeaderFooterState
Returns the active header or footer scope, or `null`.
## useNoteScopeState and useNotePropertiesState
Return the active note scope and its numbering properties.
## useContextMenuTarget
Returns the element that received the last context-menu action.
## useEditorValueCommand
Returns state and actions for value commands such as `'image.wrap'` and
`'image.altText'`.
## useTableBorderTargetLabel
Returns the label for the active table border target.
## useTranslation and useChromeTranslate
`useTranslation` returns `{ t }` for the active locale. `useChromeTranslate`
resolves labels for chrome `t` props. Pass a `Map` to override selected labels.
## Toolbar helpers
`useToolbarContext` returns the toolbar compound context.
`useToolbarLabel` returns the active scope label. `useToolbarLabelFor` resolves
a label for one slot ID. `useScopeClassName` returns the chrome class prefix.
`useScopedChromeAnchor` returns anchor data for overlay chrome.
## Review author styles
`useReviewAuthors` returns a shallow ref with each review author's `author`,
`slot`, `color`, and resolved `style`. Tracked-change authors come first.
Comment-only authors follow them.
Declare styles inside `DocxEditor.Root`:
```vue
{{ authors.length }} review authors
```
Use `DocxEditor.ColorByChangeType` to color insertions and deletions by change
type. These components change presentation only. They do not change the DOCX
author data.
## Pro Vue composables
Install `@docx-editor.dev/pro` and import these APIs from
`@docx-editor.dev/pro/vue`:
- `useReview()` returns items, active state, review actions, pane state, and
readiness.
- `useReviewOf(editorRef, query?)` binds the same state to an explicit editor
ref.
- `useReviewItem()` returns the item for the active review card context.
- `useReviewAuthor(author)` returns the resolved author style in a review rail.
- `useStackedReviewPositions(items, heights, options?)` calculates card
positions without overlap.
The package also exports `DocxEditorReview`. Mount it inside
`DocxEditor.Viewport`, beside `DocxEditor.Content`.
## Next steps
- [Vue composition](/docs/2.x/vue/composition): place custom controls.
- [Chrome slot reference](/docs/2.x/guides/chrome-slots): find every slot ID and toolbar part.
- [Vue API reference](/docs/2.x/api/vue): inspect all composable types.
---
# Composition
Source: https://www.docx-editor.dev/docs/2.x/vue/composition
`` supplies one arrangement of public parts. Use the parts when you
need a different interface.
## Composition requirements
| Requirement | When required | Purpose |
| ------------------------ | -------------------------------- | ---------------------------------------------------------------- |
| `DocxEditorRoot` | Every composed editor | Owns and provides the editor instance |
| `DocxEditorViewport` | Every composed editor | Supplies the scroll container and page-layout classes |
| `DocxEditorContent` | Every composed editor | Mounts the painted document surface |
| Remount `:key` | When you change modules | Loads the new modules |
| Positioned workspace row | When you use the navigation pane | Anchors the navigation pane |
| Pro review module | When you use Pro review chrome | Enables comments, tracked changes, and custom-node review chrome |
If you use Pro review chrome, register its modules once on
`DocxEditorRoot`. For setup, see the
[Pro package documentation](/docs/2.x/pro).
## Primitives
Every editor needs these three components in this order:
```vue
```
You can place all optional chrome inside `DocxEditorRoot`.
### Root props
`DocxEditorRoot` accepts these document-level props:
- `document`, `fonts`, `author`, `locale`, `mode`, and `modules`
- `zoom` and `zoomMode`
- `translate` and `tableInteractionLabel`
- `imageDecodePort`
Listen for `@ready`, `@change`, and `@font-error` on the root.
The root rebuilds for `document`, `fonts`, and `imageDecodePort` identity changes.
Changes to `author`, `locale`, `mode`, `translate`, `zoom`, `zoomMode`, and the locale catalog
apply without a rebuild. Existing revisions keep their authors.
The root ignores a later `modules` array change until you remount it with a different `:key`.
`locale` selects regional date input and generated document labels. It defaults to `en-US`.
For full prop types, see the
[Vue API reference](/docs/2.x/api/vue).
### UI language and date input
`DocxEditorRoot` reads UI translations from `LocaleProvider`; it has no `i18n` prop.
Set `locale` separately for regional date input. This example uses Polish for both:
```vue
```
Without the provider, UI strings remain English unless an ancestor supplies a catalog.
See [internationalization](/docs/2.x/i18n) for catalog imports and defaults.
## `provideDocxEditor()`
Call `provideDocxEditor()` when a parent layout holds the editor. The helper
provides one editor instance to the returned root and its descendants.
```vue
```
`editorRef` tracks the same instance that `useDocxEditor()` returns in
descendants.
## Customization options
Use the first option that meets your requirements:
1. Set a class or a `--doc-*` color token.
2. Replace a part's `icon` prop.
3. Use `as-child` to merge behavior into your element.
4. Override one compound part.
5. Set `:preset="false"` and arrange all parts.
6. Build your markup with
[Vue composables](/docs/2.x/vue/composables).
### Use `as-child`
`as-child` merges a part's behavior and accessibility attributes into one child.
It does not render a wrapper.
```vue
Bold
```
The child must render one element.
### Override a slot
A named part replaces the matching part in the preset. The `hidden` prop removes
that part.
```vue
```
## Custom toolbar
Set `:preset="false"` to use template order. Packaged parts still read command
state and disabled reasons from the editor.
```vue
```
Most named parts map to one `ChromeSlotId`. `Alignment` combines the four
`alignment.*` slots. The [chrome slot reference](/docs/2.x/guides/chrome-slots)
lists every slot and named React and Vue part.
### Add a host toolbar action
Use `DocxEditorToolbar.Action` for an action without a registry slot. Call
`useEditorCommand()` to read whether the command can run.
```vue
```
Reuse this logic for each surface that exposes the action.
## Custom context menu
You can keep packaged rows, remove rows, add registry slots, and add host rows.
```vue
Highlight
Page break
```
`DocxEditorContextMenu.Slot` gets its label, icon, state, and command from the
registry.
## Custom menu bar
`DocxEditorMenu` derives its default menus from `CHROME_MENUS`. Override only
the menus that your product changes.
```vue
Highlight passage
Page break
Documentation
```
Use `open-handler` and `save-handler` for composed Vue menu actions.
`` exposes these actions as `@open` and `@save`.
## Custom navigation pane
Each navigation part accepts its own class.
```vue
```
The headings list uses the document outline from the editor.
## Custom loading screen
`DocxEditorLoading` covers the time before bytes arrive and the time while a
document opens.
```vue
Opening document
```
Set `overlay` to cover the previous document while the next document opens.
```vue
```
Give `.workspace` a positioning context. Use
`DocxEditorLoading.Spinner` or `DocxEditorLoadingSpinner` to style the packaged
spinner.
## Custom hyperlink popover
`DocxEditorHyperLink` shows the link target and editing actions.
```vue
```
Its parts are `Url`, `Fields`, `Edit`, `Apply`, `Cancel`, `Copy`, `Unlink`, and
`Error`. Use `useHyperlinkPopup()` when you need different markup.
## Rulers
The rulers read page setup and zoom from the editor. A drag creates one undo
entry when you release the handle.
```vue
```
Read-only documents keep the ruler handles inactive.
## Page setup dialog
You control `DocxEditorPageSetupDialog` with `open` and `@close`.
```vue
```
Use `usePageSetup()` to build a different form.
## Content-control panel
`DocxEditorContentControl` inspects the content control at the caret.
```vue
```
The parts support the same class, `as-child`, and `hidden` controls. For content
control behavior, see
[Content controls](/docs/2.x/guides/content-controls).
## Page furniture
Mount a part only when your layout needs its interface:
| Part | Interface |
| ------------------------------ | --------------------------------------------- |
| `DocxEditorPageNumber` | Current and total page count during scrolling |
| `DocxEditorAuthorStyle` | Review style for one author |
| `DocxEditorColorByChangeType` | Tracked-change colors by change type |
| `DocxEditorFontNotice` | Rendered families without a compatible face |
| `DocxEditorDocumentOutline` | Standalone heading list with caret navigation |
| `DocxEditorHeaderFooterChrome` | Header and footer editing controls |
| `DocxEditorNotesChrome` | Footnote and endnote controls |
For review colors, see
[Tracked changes](/docs/2.x/pro/tracked-changes).
## Custom labels
Chrome resolves labels from the active locale catalog. Pass a translator only when
you need label overrides.
```vue
```
Keys outside the map continue to use the locale catalog.
## Custom colors
Set `--doc-*` tokens on a host scope. The tokens apply to chrome descendants.
```css
.my-nav {
--doc-surface: transparent;
--doc-text: #fff;
--doc-border: rgb(255 255 255 / 25%);
}
```
Do not style internal `docx-*` classes. Do not theme the document canvas. The
canvas must show the document's saved colors.
## Custom tracked changes and comments
Vue review chrome comes from `@docx-editor.dev/pro/vue`.
```vue
```
Place the review rail inside the viewport. It then scrolls with the document.
### Custom review cards
Override review card parts in place:
```vue
Nothing to review
```
Use the `item` slot for unrelated card markup:
```vue
```
Set `:preset="false"` to arrange all rail parts. Use `useReview()` for data
without chrome. Use `useReviewItem()` inside a card.
## Custom-node chrome
Import the custom-node components from the Vue Pro entry:
```vue
```
`CustomNodeChrome` applies each definition's chip color. It also dispatches
click and hover activation. `CustomNodeContextMenu` adds information, edit, and
remove rows before the packaged rows.
## Full composition
This example places the main parts by name:
```vue
```
An omitted part has no interface.
## Layout constraints
Place the navigation pane and viewport in a row with `position: relative`. The
pane uses that row as its positioning context.
Do not set `z-index` on the workspace row or viewport. Either value creates a
stacking context that can put a fixed context menu below other chrome.
## Keep the caret
A mousedown event that reaches the document moves the caret. Prevent the event
on host chrome that must keep the document selection.
```vue
```
Packaged chrome already applies this behavior.
## Next steps
- [Vue composables](/docs/2.x/vue/composables)
- [Vue props](/docs/2.x/vue/props)
- [Toolbar guide](/docs/2.x/guides/toolbar)
---
# Vue examples
Source: https://www.docx-editor.dev/docs/2.x/vue/examples
Import `@docx-editor.dev/vue/styles.css` once in your application entry before
you use these examples.
## Load DOCX bytes
```vue
```
## Save through the ref
```vue
```
## Compose custom chrome
Create a button that uses the editor context:
```vue
```
Then place the button and editor parts under the root:
```vue
```
`DocxEditor.Navigation` shares the viewport with the document.
`DocxEditor.HyperLink` and `DocxEditor.ContextMenu` provide overlay chrome.
## Automate an open editor
Render this component anywhere below `DocxEditor.Root`:
```vue
```
Install `@docx-editor.dev/editor-api` before you use this example. The runtime
uses the editor that `DocxEditor.Root` created.
## Fetch bytes and fonts together
```vue
{{ error.message }}
Loading document
```
## Live demo
The
[`examples/vue` application](https://github.com/eigenpal/docx-editor/tree/main/examples/vue)
shows composition chrome, menu overrides, rulers, navigation, and package
build mode. Run it with `bun run dev:vue`.
The [adapter parity demo](/) serves React and Vue builds from
`examples/parity/dist`.
## Next steps
- [Vue composition](/docs/2.x/vue/composition): arrange editor parts.
- [Vue composables](/docs/2.x/vue/composables): build reactive controls.
- [Toolbar guide](/docs/2.x/guides/toolbar): find command slot IDs.
- [Editing API](/docs/2.x/editor-api): automate the open document.
---
# 2.x/vue/index
Source: https://www.docx-editor.dev/docs/2.x/vue/index
## Install
```bash
npm install @docx-editor.dev/vue @docx-editor.dev/core vue
```
`@docx-editor.dev/core` and Vue 3 are peer dependencies of the adapter. Install
both with the adapter. The adapter includes `@docx-editor.dev/i18n`.
Add [`@docx-editor.dev/pro`](/docs/2.x/pro) for tracked changes and comments.
Add [`@docx-editor.dev/editor-api`](/docs/2.x/editor-api) to automate the open
document.
Import the editor stylesheet once from your application entry:
```ts
import '@docx-editor.dev/vue/styles.css';
```
The stylesheet contains the editor chrome and layout classes. Your application
must load it before you mount an editor.
## Quickstart
```vue
```
`` is the packaged editor. It includes the title bar, menu,
toolbar, navigation pane, hyperlink popover, context menu, and editable
document.
The editor has no server-rendered output. On Nuxt, render it inside
``. You can also load it with `defineAsyncComponent`.
## Root surface
- `DocxEditor`: the packaged host and compound components.
- Composables: `useDocxEditor`, `useEditorState`, `useEditorCommand`,
`useEditorEvent`, `usePageSetup`, `useParagraphIndent`, and `useFontFamily`.
- Top-level components: `DocxEditorRoot`, `DocxEditorViewport`,
`DocxEditorContent`, `DocxEditorToolbar`, `DocxEditorMenu`,
`DocxEditorNavigation`, and `DocxEditorPageSetupDialog`.
The compound includes `DocxEditor.Root`, `DocxEditor.Viewport`,
`DocxEditor.Content`, `DocxEditor.Toolbar`, `DocxEditor.Menu`,
`DocxEditor.Navigation`, `DocxEditor.HyperLink`, and
`DocxEditor.ContextMenu`.
The review module and sidebar require a Pro license. Import the Vue review
compound and composables from `@docx-editor.dev/pro/vue`.
Components, composables, and engine helpers use the package root. The package
does not provide `/ui`, `/composables`, or `/dialogs` subpaths. The stylesheet
is the only separate export.
## Composition primitives
Use the composition primitives when you need custom chrome:
```vue
```
Keep `DocxEditor.Content` inside `DocxEditor.Viewport`. Place navigation and
overlay chrome next to the content in the viewport.
## Toolbar
The package exports the toolbar as `DocxEditorToolbar`. The host also exposes
it as `DocxEditor.Toolbar`:
```vue
```
Render this button as a descendant of `DocxEditor.Root`, such as inside
`DocxEditor.Toolbar`.
Use `@mousedown.prevent` on custom chrome buttons. This keeps the document
caret in place.
## Editing API
Use `@docx-editor.dev/editor-api/browser` to automate the open document. Pass
the editor instance that the Vue adapter creates:
```ts
import { useDocxEditor } from '@docx-editor.dev/vue';
import { DocxEditor } from '@docx-editor.dev/editor-api/browser';
const editor = useDocxEditor(); // null until the content is mounted
const runtime = DocxEditor.createBrowser(editor.value!);
```
For object model details, see [Editing API](/docs/2.x/editor-api).
## Next steps
- [Vue composition](/docs/2.x/vue/composition): replace the packaged chrome.
- [Vue composables](/docs/2.x/vue/composables): build reactive controls.
- [Vue props](/docs/2.x/vue/props): configure `` and its ref.
- [Vue examples](/docs/2.x/vue/examples): use complete integration patterns.
- [Vue API reference](/docs/2.x/api/vue): inspect all public exports.
---
# Props
Source: https://www.docx-editor.dev/docs/2.x/vue/props
`` supplies the packaged host. This page groups its public props by
use. See the [Vue API reference](/docs/2.x/api/vue) for generated signatures.
## Document and mount state
Use `document` for the document source.
| Prop | Type | Description |
| ---------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `document` | `DocumentSource` | DOCX bytes, `'blank'`, or a `DocumentHandle`. |
| `author` | `string` | Author for later comments, replies, and tracked changes. |
| `locale` | `string` | BCP-47 locale for regional date input and generated labels. Defaults to `en-US`; updates without a remount. |
| `mode` | `'edit' \| 'view' \| 'suggesting'` | Editing mode. The packaged `` defaults to `'edit'`. |
| `zoom` | `number` | Numeric display scale. |
| `zoomMode` | `ZoomMode \| 'auto'` | Zoom source. `'auto'` fits the page width. |
`DocumentSource` accepts `Uint8Array`, `ArrayBuffer`, `'blank'`, or an existing
`DocumentHandle`. Resolve URLs and paths with
[`useDocxSource()`](/docs/2.x/vue/composables#usedocxsource).
```vue
```
`` rebuilds when the `document` identity changes. It applies later
`author`, `locale`, `mode`, `translate`, `zoom`, `zoomMode`, and catalog changes without a rebuild.
Existing revisions keep their authors.
For regional date input, pass `locale="en-GB"` for day/month input or
`locale="pl-PL"` for Polish dates such as `01.02.2030`. The same prop works on
`DocxEditor.Root`. It preserves dates already in the document. Use `i18n` separately
to customize UI strings; see [date input behavior](/docs/2.x/guides/fields) for details.
## Modules
`modules` registers capability modules when the editor is created.
| Prop | Type | Description |
| --------- | ------------------------- | ----------------------------------- |
| `modules` | `readonly EditorModule[]` | Modules registered at construction. |
The editor ignores later `modules` array changes.
Change the Vue `:key` to remount the editor with another module set.
```vue
```
The review module lets you show and manage tracked changes and comments. The Vue
Pro entry also exports custom-node chip and context-menu chrome.
## Chrome and layout
These props control the packaged frame:
| Prop | Type | Description |
| ---------------- | --------------------------------------- | ---------------------------------------- |
| `chrome` | `boolean` | Toggles the packaged frame. |
| `menu` | `boolean \| DocxEditorMenuProps` | Toggles or configures the menu bar. |
| `navigation` | `boolean` | Toggles the navigation pane. |
| `rulers` | `boolean` | Toggles both packaged rulers. |
| `hyperlinkPopup` | `boolean` | Toggles the hyperlink popover. |
| `contextMenu` | `boolean \| DocxEditorContextMenuProps` | Toggles or configures the context menu. |
| `t` | `(key, params?) => string` | Resolves live chrome and drawing labels. |
| `i18n` | `Translations` | Supplies a live catalog to this editor. |
Set `chrome` to `false` for the document surface without packaged chrome. You can
then build the frame from the [composition primitives](/docs/2.x/vue/composition).
## Appearance
The appearance prop name is `colorMode`.
| Prop | Type | Default | Description |
| ----------- | ------------------------------- | --------- | ---------------------------------------- |
| `colorMode` | `'light' \| 'dark' \| 'system'` | `'light'` | Sets the chrome and document color mode. |
`colorMode` applies a display transform to the document canvas. The setting does
not change authored document colors or saved output. Printing uses a light page
regardless of this setting.
```vue
```
See [Dark mode](/docs/2.x/guides/dark-mode) for canvas behavior.
## Fonts
`fonts` supplies bytes for text shaping, line wrapping, and pagination.
| Prop | Type | Description |
| ------- | ---------------------------------------------------------------- | ------------------------------------ |
| `fonts` | `FontConfiguration \| FontConfigurationFragment \| FontResolver` | Font bytes or an on-demand resolver. |
Embedded fonts load from the document. Configured fonts extend that set.
Use `packagedFonts()` for the Word default substitutes. It resolves per document,
loading a family when that document names it, or when that family is the
document's default face. So a document pays for what it declares instead of all
20 eager faces, and nothing is fetched from a third party.
The default face counts because a run that names no font still has to be measured
in one. That face is Calibri, so Carlito loads for every document.
`useFonts()` keeps one resolver identity, which the `fonts` prop needs.
```vue
```
Add an origin by adding an argument. Arguments compose first-wins:
```ts
import { googleFonts } from '@docx-editor.dev/fonts/google';
const fonts = useFonts(packagedFonts(), googleFonts());
```
`packagedFonts()` resolves after the document is parsed, so the first layout uses
fixed measurement and the editor re-paginates when the faces arrive. Edits made in
between survive that; the undo history behind them does not. For a document that
must paginate correctly on the first pass, use `defaultFonts()` instead. For more
information, see [Fonts and measurement](/docs/2.x/guides/fonts#choose-between-lazy-and-eager-loading).
See [Fonts and measurement](/docs/2.x/guides/fonts) for font sources and
on-demand loading.
## Title bar customization
Use `title` and the two named slots to customize the title bar.
| API | Type | Description |
| ---------------- | ------------------------- | ----------------------------------- |
| `title` | `string` | Shows the document name. |
| `@title-change` | `(title: string) => void` | Enables and receives title editing. |
| `#titleBarLeft` | slot | Adds host content before the title. |
| `#titleBarRight` | slot | Adds host content after the title. |
```vue
```
The title becomes editable when you listen for `@title-change`.
## Emits
`` emits these six events:
| Emit | Payload | Description |
| --------------- | ------------------------ | --------------------------------------- |
| `@ready` | `editor: Editor` | The editor and content mount are ready. |
| `@change` | `change: DocumentChange` | A document mutation completed. |
| `@save` | none | The packaged Save action ran. |
| `@open` | none | The packaged Open action ran. |
| `@title-change` | `title: string` | The editable title changed. |
| `@font-error` | `error: EditorFontError` | Font resolution failed. |
`DocumentChange` contains revision and identity changes. It does not contain
saved bytes.
```vue
```
`DocxEditorRoot` emits `ready`, `change`, and `font-error`. File and title
events belong to ``.
## Ref methods
Capture a `DocxEditorRef` for imperative document operations.
```vue
```
The ref exposes seven methods:
| Method | Returns | Description |
| ------------------------- | ------------------------------ | ---------------------------------------------- |
| `load(document)` | `void` | Loads bytes, `'blank'`, or a `DocumentHandle`. |
| `save()` | `Promise` | Serializes the current document. |
| `getDocumentHandle()` | `DocumentHandle \| null` | Returns the current handle and revision. |
| `getEditor()` | `Editor \| null` | Returns the full editor facade. |
| `focus()` | `void` | Focuses the mounted document surface. |
| `exec(command, options?)` | `ExecResult` | Runs a typed command in an optional scope. |
| `snapshot(options?)` | `EditorSnapshot` | Reads state from an optional scope. |
Use `exec()` for commands that must use the same validation as packaged chrome.
Use `snapshot()` for a synchronous state read.
## Next steps
- [Vue quickstart](/docs/2.x/vue)
- [Vue examples](/docs/2.x/vue/examples)
- [Vue composition](/docs/2.x/vue/composition)
- [Vue API reference](/docs/2.x/api/vue)
---
# Word fidelity
Source: https://www.docx-editor.dev/docs/2.x/word-fidelity
## Product behavior
| Area | Behavior |
| ------------------- | ---------------------------------------------------------------------------- |
| Browser editor | Parses, renders, edits, and serializes DOCX data in the browser. |
| Server editing API | Runs where you host it. |
| File format | Reads and writes Office Open XML (OOXML). |
| Unsupported content | Preserves unmodeled markup and package payloads when you edit other content. |
| Support | Contact [docx-editor@eigenpal.com](mailto:docx-editor@eigenpal.com). |
## Feature matrix
The matrix reports three separate support axes.
| Axis | Question |
| ---------- | --------------------------------------------- |
| Editing | Can a user or API change the feature? |
| Rendering | Does the feature display like Microsoft Word? |
| Round-trip | Does it survive open, edit, save, and reopen? |
| Status | Meaning |
| ----------- | -------------------------------------------------- |
| Full | Matches Word behavior for this feature. |
| Partial | Works with the limits in its note. |
| Render only | Displays correctly but is not editable. |
| Preserved | Remains as inert content through editing and save. |
| No | Is not supported. |
### Text and formatting
### Paragraphs and styles
Body text frames with numeric `w:x`, `w:y`, and `w:w` retain their authored positions.
Text wraps inside the frame width and remains selectable and editable.
Adjacent paragraphs with identical `w:framePr` attributes share one frame.
Frames follow their next ordinary paragraph across page breaks and do not consume normal paragraph height.
Following text wraps around frames; `none` and `notBeside` move that text below the frame.
This supports auto-height frames in single-column sections with page, margin, or text anchors.
Drop caps, fixed heights, alignment-based positions, and frames containing drawings or notes retain ordinary layout.
Upward text-relative offsets also retain ordinary layout.
Frame groups that block the full text width and leave no vertical room on a fresh page use ordinary layout.
The editor preserves their properties on save.
A following continuous section starts below the preceding frames.
There is no interface for creating, moving, or resizing text frames.
### Lists and numbering
### Tables
Explicit Word compatibility modes 11, 12, and 14 preserve content alignment for supported full-width AutoFit tables.
This applies in body, header, footer, text-box, and note stories.
The table must have explicit zero indentation, left alignment, zero cell spacing, and consistent outer cell margins.
Its complete authored grid must equal the text-column width plus those margins.
Other layouts keep their existing geometry, including nested tables and files without an explicit compatibility mode.
Body text wraps beside supported floating tables and below tables that cover the text column.
The table's text distances keep the authored gap between its border and surrounding text.
Text-anchored tables with numeric vertical offsets follow their next ordinary paragraph onto its page and column.
Negative offsets remain in place when they clear preceding text.
A table that intersects preceding text moves below that text.
Text-anchored tables taller than a content page retain row pagination.
Text-anchored tables marked no-overlap or using vertical alignment also retain their normal flow behavior.
Tables whose cells can wrap around earlier floating objects also retain row pagination.
Simple tables can share a terminal empty paragraph without shrinking that paragraph or adding a blank page.
Floating-table properties survive save, but have no editing interface.
When you compare a multipage table, check the space between each repeated header
and its first body row. The table pagination note lists the shared-border
measurement limits. Keep the original document when you report a spacing issue.
### Images and drawings
### Page layout, headers, and footers
### Tracked changes, comments, and notes
### Fields, links, and table of contents
### Document structure and content controls
### Collaboration, languages, and editing
## Important drawing limits
The matrix remains the source of truth for support status. These drawing limits
often affect document evaluation.
| Feature | Rendering | Editing | Round-trip |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------- | ---------- |
| Anchored text boxes | Shows read-only content inside the authored extent. Linked chains, autofit, and rotation can clip or use placeholders. | No | Preserved |
| Drawing shapes | Renders solid rectangles, ellipses, bounded polygons, and groups. Other shapes use a placeholder. | No | Preserved |
| WMF and EMF | Rasterizes supported files. Other files keep their extent with a placeholder. | No | Full |
## Rendering and pagination
The editor renders opaque solid Word 2010 text outlines with explicit RGB colors.
Other text effects remain preserved; the text-effects matrix lists the rendering limits.
PDF export does not paint these outlines.
The editor calculates document layout without browser flow layout. It uses
document fonts, themes, section geometry, margins, and Word units.
The layout result controls line and page breaks. The editor paints that result
as DOM text, not a canvas bitmap.
Usable font bytes are required for Word-accurate text measurement. See
[Fonts and measurement](/docs/2.x/guides/fonts).
The configured font resolver also receives faces used by SYMBOL fields and numbering markers.
Your host must supply usable font bytes for those faces when they are unavailable locally.
Unused numbering definitions do not add font requests.
East Asian layout keeps punctuation, graphemes, and full-width number groups
together across formatting changes. Justified lines distribute inter-character
spacing through the same geometry used for paint and caret placement.
East Asian font hints select the document's East Asian face for supported punctuation,
symbols, Greek, and Cyrillic ranges. Combining marks and joined emoji keep their base character's slot across text runs.
Explicit symbol fonts retain their selected face.
Existing document settings control kinsoku, Japanese strict rules, custom
language-specific break restrictions, Korean character wrapping, punctuation
overflow, and punctuation or kana compression. Compression uses deterministic
advance reductions. Font-specific optical compression is not modeled.
The feature matrix lists the support limits for East Asian typography.
For pipeline details, see [Architecture](/docs/2.x/core/architecture). Test your
documents in the [live demo](https://docx-editor.dev/editor).
## Round-trip contract
A DOCX file is a ZIP package with XML parts and binary payloads.
| Content | Save behavior |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Modeled OOXML | Serializes from typed document nodes. |
| Unmodeled XML elements and attributes | Re-emits generic nodes in place. |
| Legacy VML, custom XML, and add-in markup | Preserves structural content. |
| Media, VBA projects, embedded fonts, and OLE binaries | Copies package payloads without modification. |
| Relationship IDs, bookmark names, style IDs, and numbering definitions | Keeps references intact. |
| Tracked changes | Writes Word `w:ins` and `w:del` revisions. |
| Theme colors | Keeps theme references instead of flattening them to RGB values. |
The contract protects semantic meaning and structure. It does not promise byte
identity for rewritten XML parts.
Continuous integration checks real documents with a canonical tree fingerprint
and a save-and-reopen semantic digest. Focused tests cover revisions, fields,
hyperlinks, content controls, and browser editing.
If untouched content is lost or reinterpreted, report a
[round-trip issue](https://github.com/eigenpal/docx-editor/issues/new/choose)
with the source document.
## Security and data flow
| Concern | Behavior |
| -------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Browser uploads | The editor package does not upload documents or call a conversion service. |
| Macros | The editor preserves VBA payloads but never executes them. |
| OLE | The editor preserves OLE payloads but never executes or renders them. |
| Calculated fields | Supported calculated fields update display text only. Macros, DDE, and external includes remain inert. |
| Hyperlinks | Allowlisted targets open only after a user gesture. |
| Server editing | Your host controls where DOCX data runs. |
| Browser automation | Controls an editor already in the page. |
| External artificial intelligence | `@docx-editor.dev/editor-api` includes no model integration or transport. |
Your application controls any data sent to another service or model.
## Bundle and performance
| Area | Behavior |
| -------------- | ---------------------------------------------------------------------- |
| Core exports | Subpath exports can tree-shake independently. |
| Engine copies | Adapters use core as a peer dependency to resolve one engine copy. |
| Initial bundle | You can lazy-load the editor with your framework. |
| Layout work | Per-block caches avoid measuring unchanged blocks again. |
| Benchmarks | No published benchmark represents every document. Test your own files. |
## License and API stability
| Package or policy | Terms |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `@docx-editor.dev/core`, `@docx-editor.dev/react`, `@docx-editor.dev/vue`, and `@docx-editor.dev/i18n` | Apache 2.0 |
| `@docx-editor.dev/fonts` | Apache-2.0 AND OFL-1.1 AND LicenseRef-GUST-Font-License |
| `@docx-editor.dev/editor-api` | EigenPal Pro License |
| `@docx-editor.dev/pro` | EigenPal Pro License |
| Versions | Semantic Versioning with one fixed version group |
| Public API | CI checks generated API Extractor snapshots |
| Contributions | Require a one-time [Contributor License Agreement](https://github.com/eigenpal/docx-editor/blob/main/CLA.md) |
The Pro packages are licensed under the EigenPal Pro License, and you can compare and buy license and support levels on the [pricing page](https://www.docx-editor.dev/pricing).
## Next steps
- [Quickstart](/docs/2.x/quickstart): Load, edit, and save a DOCX file.
- [Architecture](/docs/2.x/core/architecture): Review the engine pipeline.
- [Fields and cross-references](/docs/2.x/guides/fields): Review field evaluation limits.
- [Tracked changes](/docs/2.x/pro/tracked-changes): Review revision behavior.
---