Read & write Word .docx from Node and the browser.

WordprocessingML — paragraphs, runs, tables, styles, numbering, headers / footers, sections, images, comments, fields, tracked changes — modeled, edited, and serialized. Lossless round-trip for every part the library does not yet structure, so hand-designed templates survive intact.

$ pnpm add @office-kit/docx @office-kit/docx-preview
tests 512 vitest, Node 22 + 24
bundle ~42 KB minimal slice · ~131 KB full
targets Node + browser ESM only · no Node-only deps

What it does well

04 / 04
01

Lossless round-trip

Every XML element word-kit does not yet model is preserved as a pass-through node in its original position. Opening and re-saving an unmodified template leaves Word's "needs repair" prompt out of the picture.

02

PowerPoint-style templates

Open a hand-designed .docx, merge its styles into your authoring graph with mergeStylesFromTemplate, then keep appending. No {{placeholder}} dance required for the layout itself.

03

Browser preview, no server

@office-kit/docx-preview wraps the OSS docx-preview renderer behind a single function: previewToDOM(source, container). Drop any Docx, Uint8Array, Blob, or ArrayBuffer into a DOM node.

04

Tree-shakeable function API

Every operation is a standalone export. A minimal createDocx + appendParagraph + toUint8Array slice bundles to ~42 KB minified; the full surface is ~131 KB. CI enforces both.

Two snippets to get the shape

live · type-checked

Both files below live under site/src/lib/examples/ and are type-checked by svelte-check against the real library on every build — if an API renames, the docs build fails.

01

Build a document from scratch

Heading, body paragraph, bullet list, table, A4 page size — the canonical authoring slice.

site/src/lib/examples/from-scratch.ts .ts
// Build a .docx from scratch. This file is imported as ?raw into the
// docs site so the snippet shown to readers is exactly what svelte-check
// type-checked — if an API rename breaks it, the docs build fails.

import {
  addBulletList,
  addTable,
  appendHeading,
  appendParagraph,
  createDocx,
  PAGE_SIZE_A4,
  setPageSize,
  toUint8Array,
} from "@office-kit/docx";

const doc = createDocx({ paragraphs: [] });
setPageSize(doc, PAGE_SIZE_A4);

appendHeading(doc, "Quarterly review", 1);
appendParagraph(doc, "Highlights from Q3.");
addBulletList(doc, ["Shipped preview", "Round-trip stable", "512 tests green"]);

addTable(doc, [
  ["Metric", "Q2", "Q3"],
  ["MRR", "$120k", "$148k"],
  ["Churn", "3.1%", "2.4%"],
]);

const bytes: Uint8Array = toUint8Array(doc);
void bytes;
02

Render in the browser with @office-kit/docx-preview

previewToDOM mounts a read-only render of any Docx into a DOM container. Returns an idempotent dispose handle.

site/src/lib/examples/preview-embed.ts .ts
// Render any Docx — built from scratch or opened from bytes — into a
// DOM container using @office-kit/docx-preview. The wrap is intentional: we
// share the renderer with docx-preview upstream but pin the contract
// behind word-kit's stable function-API surface.

import { openDocx } from "@office-kit/docx";
import { previewToDOM } from "@office-kit/docx-preview";

declare const bytes: Uint8Array;
declare const container: HTMLElement;

const doc = openDocx(bytes);
const handle = await previewToDOM(doc, container, {
  classPrefix: "wk-",
  inWrapper: true,
  breakPages: true,
  renderFonts: true,
});

// when you're done, detach + release internal references:
handle.dispose();

More in Getting started, Recipes, and the browser preview playground.