Core

Architecture

How the editor renders DOCX with Word fidelity: one canonical OOXML tree, a DOM-free layout pass, and painted pages that are themselves the editable surface.

There is one document model and one pipeline:

bytes → bounded OPC/XML read → canonical OOXML tree → layout → painted pages → serialize

What you see painted and what an edit mutates are the same tree, so there is no separate view model to synchronize.

Architecture boundaries

BoundaryOwnsMust not own
contractsPublic engine types and interfacesRuntime implementation or state
storeCanonical trees, package reads and writes, indexes, and transactionsDOM or ProseMirror state
layoutDOM-free pagination and interaction geometryBrowser layout or authored state
outputPainted pages from layout recordsDocument mutation
bindingThe ProseMirror projection and conversion of edits into tree operationsCanonical document authority
automationTransport-neutral host operations for browser and server automationDOM or editor chrome
editorThe public editor facade, surface input, caret, and chrome registryA second document model
react and vueProviders, hooks or composables, and framework chromeEditing state

The package boundary keeps one copy of @docx-editor.dev/core in an application. Both adapters use it as a peer dependency.

The canonical tree

Reading a .docx produces one tree per XML part, up to a bounded part count. Non-XML parts stay as bytes and are never parsed. Nodes are typed where layout needs them (the paragraph, run, and table vocabulary) and generic everywhere else, preserving the element verbatim.

Two consequences:

  • Content the engine does not model is carried, not dropped. A known element in an invalid position demotes to generic rather than erroring.
  • Unknown content never blocks editing. A document full of extensions the engine does not recognize opens, accepts edits, and saves.

Every write is a transaction over the tree. Content edits are addressed by node id and character offset; package-level operations such as creating a header or setting section properties are addressed by section index. That is the only write path. There is no second way to mutate a document, so undo, the editing API, and every command use the same write path.

Layout

The layout pass is DOM-free. It takes the tree and a text measurer and returns positioned pages, working in the document's own units (twips, half-points, EMUs) rather than approximating from browser layout.

Because it is not constrained by what contenteditable can express, it lays text out according to Word layout rules:

  • Pagination is computed, not approximated: lines are measured, blocks split where Word splits them, and tables fragment across page boundaries with Word-compatible border treatment at the split.
  • Paragraph fidelity resolves through the real style cascade: w:spacing line rules, first-line and hanging indents, w:contextualSpacing, paragraph borders, tab stops and leaders, list markers from numbering.xml, and table styles through their basedOn chain gated by w:tblLook.
  • Headers and footers lay out once per variant and attach per page. Editing one uses the same edit path as body content.

The pass is incremental: per-block cache keys and flow checkpoints mean a keystroke re-lays out what changed, and a pass with no changes returns the previous pages by identity.

Paint and interaction

The painted pages are contenteditable, but the DOM is a non-authoritative rendering. Browser mutations are prevented and re-expressed as tree operations, so the browser cannot insert markup inside your document.

The editor also paints its own caret, from layout geometry rather than from the DOM, which is why an empty paragraph gets a caret. On failure it falls back: range selection, IME composition, or a position it cannot place all restore the native caret instead of showing no caret.

Selection maps through paragraph identity and offsets. It does not use DOM traversal.

Page furnitureLayout behaviorInteraction behavior
Headers and footersLay out by section and page variantUse scoped editing controls
Page numbers and other furnitureAttach to each painted pageStay non-editable and outside selection
Body contentFlows around reserved furniture bandsUses the normal caret and selection model

Saving

Every parsed XML part is re-emitted from the canonical tree with structural fidelity, including custom XML and unknown extensions. Package payloads such as embedded fonts, media, and VBA binaries pass through untouched.

That is what "structural fidelity" means here: unsupported XML and package payloads survive editing and save without loss. Two oracles gate it in CI: a canonical fingerprint over the tree, and a save-and-reopen semantic digest.

Adapters

@docx-editor.dev/core contains the engine and no framework code. An adapter mounts the engine and provides framework-specific state access and chrome. @docx-editor.dev/react and @docx-editor.dev/vue hold no editing state.

Packaged and custom controls consume the same public engine contract.

Next steps

On this page