Word fidelity

Word feature support matrix for the DOCX editor: what renders, what edits, what round-trips with structural fidelity, plus the security model and API stability policy.

Overview

  • Browser editor use is client-side. Parsing, rendering, editing, and serialization run in the browser for normal <DocxEditor> usage. The server-side editing API runs wherever you host it.
  • .docx in, .docx out. The editor reads OOXML and writes OOXML. Untouched content, unsupported markup, and package payloads survive editing and save.
  • Apache 2.0, every published package except @docx-editor.dev/editor-api and @docx-editor.dev/pro, which are under the EigenPal Pro Evaluation License 1.0 (evaluate freely; production use needs a commercial agreement). SemVer, with the public API tracked by generated snapshots in CI.
  • Commercial support: docx-editor@eigenpal.com.

Feature matrix

Every feature is tracked on three axes:

  • Editing: can a user (or code driving the editor) change it in the editor?
  • Rendering: does it display the way Microsoft Word renders it?
  • Round-trip: does it survive open, edit, save, and reopen without loss?

A construct the editor doesn't model is retained as inert content and written back with structural fidelity; that's the "Preserved" status below.

85 features trackedFull30 editingPartial21 editingNo34 editing

Legend: Full = works like Word · Partial = works with noted limits · Render only = displays correctly, not editable · Preserved = carried inertly through editing and save · Planned = on the roadmap · No = not supported.

Text & formatting

FeatureEditingRenderingRound-tripNotes
Bold, italic, underline, strikethroughFullFullFull
Subscript & superscriptFullFullFull
Font family & sizeFullFullFullCustom fonts registered via the fonts prop (loadFonts fetches and hash-verifies app-specified URLs); theme fonts resolved from the OOXML theme. Word-accurate wrap and pagination need font bytes for shaped measurement — the optional @docx-editor.dev/fonts package supplies metric-compatible substitutes for the Word defaults (Carlito, Caladea, Liberation). The fonts prop also accepts a resolver called once per load with the families a document declares, so an app can opt into loading only those; googleFonts() serves them from a pinned, hash-checked catalog.
Embedded fontsPartialFullPreservedFonts embedded in the file (word/fonts) are de-obfuscated and wired into shaped text measurement automatically on load — no configuration or network. The embedded binaries round-trip on save; the editor does not add new embedded fonts.
Text color (RGB + theme colors)FullFullFullTheme color references (accent1...) round-trip as references, not flattened to hex.
Highlight & shadingFullFullFullWord highlight palette plus arbitrary w:shd fills.
Right-to-left & bidirectional textFullFullFullBidi layout with mirrored alignment; Hebrew locale ships in @docx-editor.dev/i18n.
Text effects (outline, shadow, emboss, emphasis mark)NoFullFullw:outline, w:shadow, w:emboss, w:imprint and w:em render and round-trip; not settable from the toolbar. w14 glow and gradient text fill are not supported.
Hidden text (vanish)NoFullFullw:vanish runs are not drawn and take no space, so pages break where Word breaks them; the text survives a round trip. There is no "show hidden text" view option, and a paragraph whose MARK is vanished still occupies a line.
Math equations (OMML)NoPartialFullEquations round-trip verbatim (raw OMML) and show a styled text fallback. Laid-out math and equation editing are not built yet.
Symbol characters (w:sym)PartialFullFullSymbol runs render and survive ProseMirror edits and save. Symbols can be inserted from the Insert menu, but existing symbol-run properties are not directly editable.

Paragraphs & styles

FeatureEditingRenderingRound-tripNotes
Alignment & justificationFullFullFull
Line & paragraph spacingFullFullFullSpace before/after and line spacing (single, multiple, exactly, at least) all reach pagination, so a 1.5- or double-spaced document breaks pages where Word breaks them. Font external leading is excluded from line boxes, and trailing auto-spacing may cross the bottom text margin when the glyphs fit, matching Word’s vertical pagination. The paragraph mark’s w:sz participates in the last line’s metrics, matching Word when a cover-page mark is taller than the visible runs. Contextual spacing drops the gap between same-style neighbours, the way Word’s List Paragraph style intends. Automatic spacing (w:beforeAutospacing / w:afterAutospacing) replaces the authored measurement with 14pt in body paragraphs and 0pt in list items and table cells.
Indentation (incl. hanging indents)FullFullFullLeft, right, first-line and hanging indents all reach line geometry, so an indented first line starts where Word starts it and wraps with the room it actually has. Increase/Decrease Indent is on the toolbar and on Tab / Ctrl+M; inside a list it changes the level, so the marker changes with it.
Paragraph styles (Heading 1, Quote, custom styles)FullFullFullStyle picker applies document styles, including custom styles with their numbering and indents. Defining new styles in the UI is not supported yet.
Paragraph borders & fillsPartialFullFullParagraph shading (w:shd) is editable. Borders render common ST_Border line styles (single, double, dashed, dotted, and CSS approximations for thick/3-D/inset/outset); decorative art borders paint as a solid rule. Thin doubles inflate to a visible compound band in layout, matching table borders. Borders round-trip but cannot be added, changed or removed from the editor yet.
Tab stops & leadersPartialFullFullExisting tab stops render, including right/decimal tabs and dot, hyphen and underscore leaders. Positional tabs (w:ptab) render too, so a table-of-contents line reads as one: entry left, leader dots between, page number flush right. The document's own w:defaultTabStop is honoured, so a metric-locale grid lands where Word puts it, in headers and footers as well as the body. A tab-stop editing UI is not built yet.
Drop caps & text frames (framePr)NoNoFullParsed and round-tripped; text flows inline rather than as a drop cap or positioned frame.
Automatic hyphenationNoNoPreservedDocument hyphenation settings round-trip; the layout engine does not hyphenate.

Lists & numbering

FeatureEditingRenderingRound-tripNotes
Bullet lists (multi-level)FullFullFullToolbar toggle creates the definition on first use, numbering.xml included, so a document that has never carried a list can start one. Tab and the indent buttons change the level, and the marker changes with it.
Numbered lists (decimal, roman, letters)FullFullFull
Custom numbering definitions & style-linked numberingFullFullFullNumbering attached to custom paragraph styles resolves with Word’s precedence rules.
List continuation & restartFullFullFull
Picture bullets (numPicBullet)NoNoPreservedNot rendered or editable; the numPicBullet definition and its authored markup are preserved on save.

Tables

FeatureEditingRenderingRound-tripNotes
Table insertion & cell editingFullFullFull
Row/column insert, delete, resizeFullFullFullCore store and React paginated editor: hover row/column insertion, adjacent divider and outer-right resize, and seven table context-menu structural actions. Vue toolbar and context-menu value UI remain deferred; Vue inherits shared command types only. Tables remain read-only in the automation object model.
Cell borders & shadingPartialFullFullSelected-cell borders and fill through React contextual toolbar controls (allowlisted styles, nullable clear fill). Vue value chrome deferred. Existing table/cell borders and table-style shading still render and round-trip.
Merged cells (horizontal & vertical)NoFullFullAuthored merges render and round-trip. Merge and split commands are declared but refused; column insert/delete/resize on merged tables shows the engine reason.
Tables split across pagesFullFullFullRows split mid-content with correct cut borders; vertically merged cells repaint on continuation pages like Word.
Nested tablesPartialFullFullInnermost nested table owns resize furniture, structural edits, and selected-cell borders/fill in the React editor; outer tables stay isolated through save/reopen. Vue table chrome deferred.
Table styles & conditional formatting (header row, banding)PartialFullFullTable styles resolve through their basedOn chain: borders, cell margins, shading and the paragraph/run formatting a conditional format carries (so a header row comes out bold and centred) all come from styles.xml, gated by w:tblLook, with an explicit w:cnfStyle taking precedence. Conditional cell margins and switching table styles from the UI are not built yet.
Floating tables (tblpPr anchored position)NoPartialFullAn anchored table lands where Word puts it across the page: tblpXSpec/tblpX against the text, margin or page box, plus a tblpY offset from the text anchor. Text does not yet wrap beside it, and page- or margin-anchored vertical positions keep their place in the flow.
Vertical cell text (textDirection)NoFullFulltbRl/btLr cell text renders via writing-mode and round-trips; not settable from the UI.

Images & drawings

FeatureEditingRenderingRound-tripNotes
Inline images (paste, drag-drop, resize)PartialFullFullEngine layout and paint for embedded PNG/JPEG/GIF at authored wp:extent; React insert/overlay authoring (toolbar, properties, keyboard resize). Vue authoring UI deferred to vue-drawing-authoring-parity — shared engine commands only.
Floating images & wrap modes (square, topAndBottom...)PartialFullFullNine wrap choices, exclusion reflow, z-order, and anchored drag/resize in React. Vue wrap/alt/properties chrome deferred; engine setImageWrapType and toolbarCommandState are shared.
BMP and WebP imagesNoFullFullDecoded natively by the browser and painted at the authored size, like PNG or JPEG. BMP covers what older documents carry (including top-down bitmaps and the 12-byte BITMAPCOREHEADER); WebP covers lossy, lossless and extended containers. Inserting a new one is not supported yet.
SVG imagesNoFullFullEmbedded SVG paints at the authored size. Rendered in the browser secure static mode, so scripts and external references inside the file stay inert. Inserting a new SVG is not supported yet.
WMF / EMF legacy vector imagesNoPartialFullRasterized in the browser and painted at the authored extent. A metafile that will not convert keeps its extent and a labelled placeholder. Original bytes round-trip untouched.
TIFF imagesNoPartialFullBaseline TIFF is decoded in the browser and painted at the authored extent; the first page of a multi-page file is used. A flavour that will not decode keeps its extent and a labelled placeholder. Inserting a new TIFF is not supported yet.
Tracked image insert/deleteNoPreservedPreservedRevision wrappers preserved inertly; accept/reject and suggesting-mode delete owned by typed-revisions-and-comments.
Text boxesNoPartialPreservedAnchored text boxes render their story content clipped inside the extent — in the body, headers, and footers, including page-relative anchors — with PAGE / NUMPAGES / SECTIONPAGES fields inside header/footer text boxes evaluated per page. Read-only: inner stories are not editable. Inline text boxes, linked chains, autofit, and rotation still render as placeholders or clip.
Drawing shapes & geometryNoPartialPreservedCharts, groups, canvases, and custom geometry reserve extent with placeholders; unsupported payloads stay generic in the canonical tree.
Picture cropping (srcRect)PartialFullFullCrop renders and round-trips; React properties dialog edits crop in UI percent. Vue deferred.
Picture adjustments (brightness, contrast, recolor)NoPartialFullTransparency, brightness, contrast and grayscale project where supported; authored adjustment markup is preserved on save.
Picture effects (shadow, glow, reflection)NoNoPreservedNot painted or editable; authored effect markup and effectExtent spacing are preserved.
Charts (DrawingML)NoPartialPreservedExtent reserved with labelled placeholder; chart payload preserved generically, not semantically edited.
SmartArt & diagramsNoPartialPreservedSame placeholder policy as charts; payload preserved inertly.
Ink annotations (w:ink)NoNoPreservedNot rendered or editable; ink markup is preserved generically on save.

Page layout, headers & footers

FeatureEditingRenderingRound-tripNotes
True pagination (Word-metric pages)FullFullFullThe layout engine paginates like Word: page breaks, keep rules, split paragraphs marked across pages. Hard page breaks are insertable and write `w:br w:type="page"`.
Sections (margins, size, orientation, per-section headers)PartialFullFullPage size, orientation and margins editable per section or whole document (Page Setup dialog, ruler drags); each section paginates against its own geometry, so mixed portrait/landscape documents render as Word shows them. Section breaks insertable. Even/odd-page break parity (the blank page Word inserts to reach the right parity) and per-section columns are not modelled yet.
Headers & footers (edit in place)PartialFullFullReact: typed scoped header/footer editing (enter/exit story, create/remove, link/unlink to previous, title-page and even/odd options) with PAGE/NUMPAGES/SECTIONPAGES insert chrome. `editHeaderFooter` accepts `variant` / `evenPage` / `firstPage` on the shared Editor contract. Per-section first/even/default variants paint like Word. Vue chrome deferred; Vue can still call the shared commands. Tracked changes, watermark/drawing authoring, and structural table ops inside furniture are not claimed.
Watermarks (text & image)NoPlannedPreservedWatermarks live as VML/drawings inside header parts. Typing, layout, and editing are deferred to the drawings lane; Editor.getWatermark() is a stub. Structural markup may survive in the header part but is not a supported watermark feature.
Footnotes & endnotesPartialPartialFullReact: typed note model, layout (pageBottom/beneathText/sectEnd/docEnd), scoped note editing, insert/delete/convert, chrome slots. Vue deferred. Tracked note inserts and notes-in-HF layout out of scope.
Multi-column layoutNoPartialFullSection w:cols count, gap, separator and equal/unequal widths paginate into columns; explicit column breaks leave the break paragraph's empty remainder at the top of the next column. Balancing continuous multi-column sections is supported. Column editing chrome is not exposed.
Page bordersNoFullFullRender with z-order, offset modes and first-page filters; not editable from the UI.
Line numbers (lnNumType)NoNoFullParsed and round-tripped; not drawn in the margin.
Different even & odd headersPartialFullFullFirst, even, and default variants are selected by the page's number in the document (so the alternation carries across section breaks) and editable in an open furniture scope. Programmatic `editHeaderFooter({ variant: 'even' })` (or `evenPage: true`) creates/opens the even story and enables `w:evenAndOddHeaders` in one undo unit. React header/footer chrome can toggle different even and odd pages; Vue chrome deferred.
Section vertical alignment (vAlign)NoNoFullRound-trips; page content stays top-aligned.
Page background color/image (w:background)NoNoPreservedNot rendered or editable; authored background markup and relationships are preserved.
Page number format (pgNumType)NoPartialFullSection numbering start, format, chapter style, and chapter separator parse and serialize. Allowlisted PAGE fields in headers/footers honour authored start and fmt (e.g. lowerRoman); NUMPAGES/SECTIONPAGES stay decimal. There is no pgNumType authoring UI yet.

Review: tracked changes, comments, notes

FeatureEditingRenderingRound-tripNotes
Tracked changes (insert, delete, format)FullFullFullFull revision model incl. structural changes (paragraph breaks, paragraph props, table rows/cells). Opens cleanly in Word’s review pane.
Accept / reject changes (UI + API)FullFullFullPer-change accept/reject in the sidebar and through acceptReviewItem/rejectReviewItem, plus revision.accept()/reject() and whole-document revisions.acceptAll()/rejectAll() through the automation object model. The sidebar itself offers no bulk control: resolve the queue with the per-item call over every item.
Comments (threads, replies, resolve)FullFullFull
Programmatic redlining (code-proposed tracked changes)FullFullFullWord-native tracked changes written through the automation object model, against DOCX bytes on a server or an editor open in a page.
Tracked moves (move from/to)PartialFullFullImported moves render distinctly from insert/delete and round-trip.
FeatureEditingRenderingRound-tripNotes
Hyperlinks (external)FullFullFullInsert, edit and remove with Ctrl/Cmd+K or the toolbar. Targets are allowlisted (http(s), mailto, tel, ftp); anything else renders inert and still round-trips. Opening a document never requests a link target — activation is an explicit gesture.
Bookmarks & internal linksPartialFullFullInternal links jump to their bookmark and move the caret, including targets on pages that have not been painted yet. Creating and renaming bookmarks is deferred.
PAGE / NUMPAGES / SECTIONPAGES fieldsPartialFullFullAllowlisted complex PAGE, NUMPAGES, and SECTIONPAGES project in headers/footers at layout time (PAGE respects section pgNumType start/fmt), including fields hosted inside anchored header/footer text boxes. Insertable from React header/footer chrome (including Page X of Y). Other field instructions stay inert; body field evaluation is deferred.
Table of contentsPartialFullFullBody TOCs can be inserted from the shared Insert menu and refreshed from document headings, including page-numbers-only updates, tab leaders, section-formatted page numbers, and bookmark links. Generated rows are read-only navigation links.
Other field codes (DATE, REF, MERGEFIELD...)NoPartialPreservedLast-computed field results display; the field codes themselves round-trip untouched.
Citations & bibliographyNoNoPreservedCITATION/BIBLIOGRAPHY fields remain inert and the b:Sources store is preserved; citation evaluation and editing are not supported.
Legacy form fields (FORMTEXT, FORMCHECKBOX, FORMDROPDOWN)NoPartialPreservedThe field result shows as static text; w:ffData, including checkbox state and constraints, is preserved but the control is not interactive.

Document structure & content controls

FeatureEditingRenderingRound-tripNotes
Content controls (SDT): block, inlinePartialFullFullBlock, inline, row and cell controls are typed and addressable in every story (table cells, headers, footers and note bodies included); a control around a table row or cell lays out as that row or cell, keeping its grid column, span and row semantics. Discover, create, fill and remove them by tag, title or file id from the document object model; content is editable, and tag, title and lock are writable through the API but have no toolbar chrome. All four `w:lock` modes are enforced against what an edit would actually change — including the characters inside an inline control, a tracked-change decision, and a hyperlink write — an enclosing control’s lock wins over an inner one, and text typed at a control’s leading edge counts as inside it, because that is where Word puts it. A write addressed at one control is resolved against every control it would actually land in, so filling in an outer control cannot put text inside a locked or bound control nested at its edge — including an empty paragraph such a control holds, where the write has to create the run it lands in. Replacing a control’s whole value, or deleting a control together with its content, is refused when it would destroy a locked or bound control nested inside it; removing the wrapper while keeping the content leaves those controls untouched and is allowed. A control’s lock protects the control and its content, not the document: page setup, section furniture and note numbering stay editable beside a locked field. Under `w:documentProtection w:edit="forms"` only control content is editable, resolved from what an edit addresses — so an inline field can be filled in while the sentence around it stays read-only. Picture and repeating-section controls, custom-XML-bound controls and docPart galleries are preserved as they were rather than typed; every edit inside a bound control is refused instead of desynchronising it from its part, while removing the control is allowed and takes the binding with it.
Repeating section controlsPartialFullFullAdd and remove items from the editor; the section configuration itself is read-only. A repeating section is not typed as a content control in the document object model — it is preserved as authored, so a script reaches the controls inside it rather than the section itself.
Dropdown, checkbox & date controlsFullFullFullEach control takes the value its own type accepts: a dropdown must name an item it declares, a combo box also takes free text, a date validates an ISO instant and writes both `w:fullDate` and the formatted text, and a checkbox writes its declared glyph and state together. A literal prompt is replaced whole on the first write; without a durable prompt source, clearing the value later leaves the control empty. A `w:temporary` control removes its own wrapper on the first edit and leaves the content.
Custom XML parts & data bindingNoNoPreservedcustomXml parts and w:dataBinding round-trip with structural fidelity; no binding evaluation.
VBA macrosNoNoPreservedNever executed, by design (client-side security); the vbaProject part survives open -> save.
OLE & embedded objectsNoNoPreservedNever executed or rendered; OLE markup and embedded binary payloads are preserved through editing and save.
Document protection & editing restrictionsPartialNoPreservedProtection settings round-trip. Forms protection is enforced: only addressed content-control content remains editable, while the surrounding document stays read-only. Other protection modes are not enforced, and inline permission ranges may be dropped.

Collaboration, i18n & editing UX

FeatureEditingRenderingRound-tripNotes
Realtime collaboration (Yjs)FullFullFullLive cursors, presence, comment sync, per-author tracked-change attribution.
Find & replaceFullFullFull
Rich copy/paste (HTML clipboard)FullFullFull
Undo / redoFullFullFull
Editor UI in 9 languagesFullFullFullen, de, fr, he, hi, pl, pt-BR, tr, zh-CN via @docx-editor.dev/i18n.
Document automation object modelFullFullFullBatching object model shaped after a documented subset of the Word JavaScript API; server entry over bytes, browser entry over an open editor. No model integration, tool catalog or MCP transport ships with it.

Rendering fidelity

The editor does not let the browser lay out the document. A DOM-free layout pass positions every line and page break from Word's own metrics (twips, the document's fonts and themes, its section and margin geometry) and the visible pages are painted from that result. The browser never decides where a line or page breaks; the layout pass does. The output is plain DOM text rather than a canvas bitmap.

How the pipeline achieves this is in Architecture. To judge fidelity yourself, open one of your own documents in the live demo next to Word.

Drawings, shapes and legacy images

These rows in the "Images & drawings" matrix are not Full yet, but their authored markup and payloads survive editing and save.

  • Text boxes: story content renders read-only inside the authored extent; editing, linked chains, autofit, and rotation are not built yet.
  • Drawing shapes: unsupported geometry reserves its extent with a placeholder; the original markup is preserved.
  • WMF / EMF: rasterized where supported, otherwise shown as a placeholder; the original media bytes are preserved.

Round-trip behavior

A .docx file is a ZIP of XML parts, and Word writes a lot of XML the editor has no reason to model: bookmarks, custom XML parts, mail-merge fields, compatibility settings, VBA projects.

This editor's pipeline is parse → document model → edit → serialize. On save, the canonical tree preserves the structure and semantics of every parsed XML part, while package payloads pass through untouched. Concretely:

  • Unmodeled XML elements and attributes are kept as generic nodes and re-emitted in place, including legacy VML, custom XML, and add-in markup.
  • Non-XML payloads such as media, VBA projects, embedded fonts, and OLE binaries are copied through the ZIP untouched.
  • References stay intact: relationship IDs, bookmark names, field codes, style IDs and numbering definitions are not renamed or renumbered.
  • Output is canonical OOXML, not merely valid: tracked changes are real w:ins/w:del revisions Word's review pane understands, theme colors stay theme references, numbering keeps its definitions.

How it's tested: CI round-trips a corpus of real documents through two oracles, a canonical fingerprint over the tree and a save-and-reopen semantic digest; serializer tests assert specific constructs (revisions, fields, hyperlinks, content controls) survive; and browser end-to-end tests drive the painted editor.

The preservation contract is semantic and structural: content you did not edit must not be lost or reinterpreted. A document that breaks this behavior is a bug; attach it to a GitHub issue.

Security

  • Normal editor use is browser-only. The editor package does not upload documents or call a conversion API.
  • No macro execution. VBA macros and embedded code are not evaluated.
  • The editing API is host-controlled. @docx-editor.dev/editor-api ships no network transport and no model integration: the browser entry drives an editor already in the page and the DOCX never leaves it, and the server entry runs wherever you host it. What reaches a model is whatever your own code sends.

Bundle and performance

  • -core ships subpath exports that tree-shake independently.
  • The engine is a peer dependency of the adapters, so one copy resolves for the whole tree.
  • Lazy-load the editor (next/dynamic, React.lazy) to keep it out of the initial bundle.
  • The layout engine caches per-block measurements; an edit doesn't re-measure the document.

No published benchmarks; test with your own documents in the live demo.

Stability, license and support

  • Apache 2.0, every package except @docx-editor.dev/editor-api and @docx-editor.dev/pro, which ship under the EigenPal Pro Evaluation License 1.0: free to evaluate internally, production use under a written agreement from licensing@eigenpal.com.
  • SemVer via changesets; all packages share one version number.
  • The public API is tracked by API Extractor snapshots; CI fails on drift.
  • Contributions need a one-time CLA.

If a feature you need is Partial or missing, open an issue with a sample .docx, or email docx-editor@eigenpal.com for priority features and support contracts.

Next steps

On this page