npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@extend-ai/react-docx

v0.14.4

Published

JavaScript and React viewers for DOCX documents

Readme

@extend-ai/react-docx

JavaScript and React viewing and editing components for rendering .docx files in a browser.

@extend-ai/react-docx gives you:

  • A shared viewer for read-only previews and editable documents
  • A controller API for import/export and custom DOCX editing UIs
  • Pagination, page layout, theme, tracked-change, form-field, and thumbnail hooks
  • Configurable page surface and inter-page background colors
  • A dark read-only night-reader mode that inverts document content while preserving image hues
  • Lower-level OOXML, model, layout, and serialization exports for custom pipelines

Install

pnpm add @extend-ai/react-docx

For the React entry, install react and react-dom as well. The vanilla entry bundles Preact and does not require React.

Benchmarks and chart accuracy

See Rust Office for parser benchmarks, accuracy results, and chart comparisons with Microsoft Word.

WebAssembly Asset

DOCX parsing and serialization run in a Rust/WebAssembly module that ships inside this package as dist/docx_wasm_bg.wasm (~2.5 MB raw, ~1 MB over the wire with gzip). It is not part of the JavaScript bundle:

  • It loads lazily, on the first call that parses or serializes a document.
  • The loader references it as new URL("./docx_wasm_bg.wasm", import.meta.url), which most Vite, webpack 5, Rollup, and Next.js builds emit as a hashed static asset.
  • In Node (SSR, tests, scripts) the binary is read from node_modules on disk.

If your deployed app returns 404 for the .wasm file, or if you need to host the binary somewhere else (e.g. a CDN), override the source before the first parse:

import { setWasmSource } from "@extend-ai/react-docx";

setWasmSource("https://cdn.example.com/docx_wasm_bg.wasm");
// or pass a URL, Response, ArrayBuffer/TypedArray, or compiled WebAssembly.Module

The binary is also exposed as a package subpath, so with Vite you can do:

import wasmUrl from "@extend-ai/react-docx/docx_wasm_bg.wasm?url";
import { setWasmSource } from "@extend-ai/react-docx";

setWasmSource(wasmUrl);

Configured string, URL, Request URL, bytes, and WebAssembly.Module sources are forwarded into the DOCX import worker. Response sources are supported on the main thread; imports automatically avoid the worker for those.

You can also call initWasm() (optionally with a source) ahead of time to warm the module before the first document is opened. Vanilla applications can import setWasmSource and initWasm from @extend-ai/react-docx/vanilla. The React and vanilla entries share the same WASM configuration.

In Vite development, exclude the vanilla entry from dependency optimization so Vite can resolve the import worker asset. Production builds do not need this setting:

import { defineConfig } from "vite";

export default defineConfig({
  optimizeDeps: { exclude: ["@extend-ai/react-docx/vanilla"] },
});

Main Entry Points

@extend-ai/react-docx/vanilla exports createDocxViewer, the DocxViewer instance class, and utilities for JavaScript applications.

Use useDocxEditor + DocxEditorViewer for both read-only and editable experiences. The hook manages document state, import/export, and editing commands; the component renders the document. Set mode="read-only" for view-only use or mode="edit" for editing.

It also re-exports lower-level document/model/layout/serializer APIs, so you can work below the UI layer when needed.

Quick Start

Vanilla JavaScript

Mount a viewer in an existing element using a browser bundler such as Vite. The viewer uses a bundled Preact renderer and creates a div in the regular DOM.

Run pnpm dev from the repository root and open /vanilla.html on the local playground URL to try the standalone demo.

Open a document from a URL

<div id="viewer" style="height: 600px; overflow: auto"></div>
<script type="module" src="/main.js"></script>
import { createDocxViewer } from "@extend-ai/react-docx/vanilla";

const viewer = createDocxViewer(document.getElementById("viewer"), {
  src: "/document.docx",
});

The URL must be accessible to the browser. A document on another origin needs appropriate CORS headers. The viewer defaults to mode: "read-only"; use mode: "edit" to enable editing.

You can also construct an instance directly:

import { DocxViewer } from "@extend-ai/react-docx/vanilla";

const viewer = new DocxViewer(document.getElementById("viewer"), {
  src: "/document.docx",
  mode: "edit",
});

Open a local document

This example starts with an empty viewer and loads a document selected by the user:

<input id="file" type="file" accept=".docx,.doc" />
<div id="viewer" style="height: 600px; overflow: auto"></div>
<script type="module" src="/main.js"></script>
import { createDocxViewer } from "@extend-ai/react-docx/vanilla";

const viewer = createDocxViewer(document.getElementById("viewer"), {
  emptyState: "Choose a document to preview.",
});

document.getElementById("file").addEventListener("change", (event) => {
  const file = event.currentTarget.files?.[0];
  if (file) viewer.setOptions({ file });
});

file accepts a File, Blob, or ArrayBuffer. A File uses its own name by default; a Blob or ArrayBuffer uses document.docx unless fileName is supplied. An explicit fileName must end in .docx or .doc.

Read state and control the viewer

Subscribe to loading, errors, pagination, editing, and other controller updates:

const unsubscribe = viewer.subscribe((controller) => {
  if (controller.isLoading) {
    console.log("Loading document");
  } else if (controller.error) {
    console.error(controller.error);
  } else if (controller.hasDocument) {
    console.log(controller.fileName, controller.currentPage, controller.totalPages);
  }
});

Read the current controller when invoking commands. For example, after the document has loaded:

viewer.setOptions({ mode: "edit" });

const controller = viewer.getController();
if (controller?.hasDocument && !controller.isLoading) {
  controller.setZoom("fit-width");
  controller.exportDocx();
}

exportDocx() downloads the current document in the browser, and getDocxBytes() resolves to the same bytes as an ArrayBuffer for saving elsewhere, such as uploading to your own storage. The controller also exposes commands such as undo(), redo(), and setDocumentTheme().

Change options or load another document with setOptions:

viewer.setOptions({ mode: "read-only", pageGapBackgroundColor: "#f3f4f6" });
viewer.setOptions({ src: "/another-document.docx", fileName: undefined });

Supplying file clears the previous src, and supplying src clears the previous file. Passing both in the same update throws. An explicit fileName remains in effect until changed or cleared with fileName: undefined. Changing the source, its name, parsed document, or starter model creates a fresh controller; other viewer options update the existing viewer.

The initialDocumentTheme option applies when a controller is created. To change the current document's theme and retain it when loading another source:

viewer.setOptions({ initialDocumentTheme: "dark" });
viewer.getController()?.setDocumentTheme("dark");

When the viewer is no longer needed, unsubscribe and release its resources:

unsubscribe();
viewer.destroy();

createDocxViewer returns an instance with these methods:

  • getController() returns the latest DocxVanillaViewerController, or null before it is available and after destruction. The controller includes the editor's commands and state, plus isLoading, error, and hasDocument.
  • subscribe(listener) reports controller updates and immediately calls the listener when a controller is available. It returns an unsubscribe function; updates can be coalesced.
  • setOptions(options) merges new options into the current configuration.
  • reload() discards the current viewer state and reloads the configured source.
  • destroy() unmounts the viewer, removes its element, and releases listeners. It is safe to call more than once.

DocxVanillaViewerOptions supports page virtualization, zoom, themes, page colors, tracked changes, comments, and context menus. emptyState, loadingState, errorState, and custom card and context-menu renderers accept text or DOM nodes. The vanilla entry also exports parseDocxForViewer, setWasmSource, and initWasm.

Read-only viewer

Starting in v0.10.0, the intended pattern for read-only viewing is useDocxEditor with DocxEditorViewer mode="read-only". Use the same component with mode="edit" when editing is needed.

import { DocxEditorViewer, useDocxEditor } from "@extend-ai/react-docx";

export function ReadOnlyDocxExample() {
  const editor = useDocxEditor();

  return (
    <div style={{ display: "grid", gap: 12 }}>
      <input
        type="file"
        accept=".docx"
        onChange={(event) => {
          const file = event.target.files?.[0];
          if (file) {
            void editor.importDocxFile(file);
          }
        }}
      />

      <DocxEditorViewer editor={editor} mode="read-only" />
    </div>
  );
}

For an already parsed document, initialize the controller with useDocxEditor({ document }). For a prebuilt document model, use useDocxEditor({ starterModel: model }). To start from a raw buffer, call parseDocxForViewer(buffer) and pass the resulting document to the hook. These options set the initial document; use editor.importDocxFile(file) to load a different document into an existing controller.

Full editor/viewer

Use useDocxEditor when you want import/export controls, document theme state, selection-aware editing commands, pagination state, and a customizable document canvas.

import * as React from "react";
import {
  DocxEditorViewer,
  useDocxDocumentTheme,
  useDocxEditor,
  useDocxPagination,
} from "@extend-ai/react-docx";

export function EditorExample() {
  const editor = useDocxEditor({
    initialDocumentTheme: "light",
    initialStatus: "Ready",
  });

  const { documentTheme, toggleDocumentTheme } = useDocxDocumentTheme(editor);
  const { pagination } = useDocxPagination(editor);

  return (
    <div style={{ display: "grid", gap: 12 }}>
      <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
        <input
          type="file"
          accept=".docx"
          onChange={(event) => {
            const file = event.target.files?.[0];
            if (file) {
              void editor.importDocxFile(file);
            }
          }}
        />

        <button type="button" onClick={() => editor.exportDocx()}>
          Export DOCX
        </button>

        <button type="button" onClick={() => toggleDocumentTheme()}>
          Theme: {documentTheme}
        </button>

        <span>
          Page {pagination.currentPage} / {pagination.totalPages}
        </span>

        <span>{editor.status}</span>
      </div>

      <DocxEditorViewer
        editor={editor}
        mode="edit"
        pageBackgroundColor="#ffffff"
        pageGapBackgroundColor="transparent"
      />
    </div>
  );
}

Thumbnail-only rendering

Use the imperative renderer when a cover or page preview is needed before the full viewer mounts. The parsed document can be passed to useDocxEditor later, so the source is read and parsed only once.

import {
  createDocxThumbnailRenderer,
  parseDocxForViewer,
  useDocxEditor,
} from "@extend-ai/react-docx";

const document = await parseDocxForViewer(file, {
  loadEmbeddedFonts: false,
});
const renderer = createDocxThumbnailRenderer(document, {
  scheduling: "immediate",
  pixelRatio: 1,
  pageIndexes: [0],
});
const cover = await renderer.renderPage(0, {
  maxWidth: 240,
  output: "blob",
});

function FullEditor() {
  const editor = useDocxEditor({ document });
  return <DocxEditorViewer editor={editor} />;
}

Responsive zoom

DocxEditorViewer accepts numeric percentages and responsive zoom modes. Responsive modes remain selected while the viewer recalculates their resolved percentage as its viewport or page layout changes.

<DocxEditorViewer
  editor={editor}
  mode="read-only"
  defaultZoom="fit-width"
/>

Use zoom with onZoomChange for a controlled viewer, or defaultZoom for an uncontrolled viewer. The available modes are "automatic", "fit-page", and "fit-width". Custom editor toolbars can use editor.setZoom(level), editor.getZoom(), and editor.getResolvedZoom(). When zooming in or out from a responsive mode, calculate the next numeric percentage from editor.getResolvedZoom() and pass it to editor.setZoom().

renderPage supports canvas, blob, and imageBitmap output. Pass canvas to draw into a caller-owned canvas without an encoding round trip. Its timings separate pagination, page mounting, rasterization, and encoding; the parsed document timings separately expose file reading, parsing, model construction, and embedded-font loading. Use loadEmbeddedFonts: "defer" to load fonts later through document.loadEmbeddedFonts(). The renderer also accepts an existing DocModel directly when the OOXML package is not needed.

Examples

See @extend-ai/react-docx in action in these live Extend UI examples. Click a screenshot to open the demo and integration docs.

DOCX Viewer

Preview paginated documents with zoom and page navigation.

DOCX viewer with a paginated document and zoom controls

DOCX Editor

Try the experimental editor with document formatting, tracked changes, and export.

DOCX editor with a document and formatting toolbar

Caller fonts

The viewer prepares shared fallbacks for Calibri, Arial, Times New Roman, Courier New, Aptos and Aptos Narrow before pagination. It loads only requested families, preserving embedded and installed originals. Chart SVG images embed the supported font families they use because image documents cannot inherit the page's fonts. Font payloads are lazy chunks; deploy them alongside the viewer. Redistribution notices are in dist/Office-Font-Licenses.txt.

Fonts embedded in a DOCX load automatically by default. Caller-provided fonts must be available in document.fonts before layout uses them. When adding or removing fonts after parsing or mounting, call refreshDocxFontMetrics() after the change to refresh cached text measurements and mounted viewers.

import { refreshDocxFontMetrics } from "@extend-ai/react-docx";

async function attachViewerFont(face: FontFace) {
  await face.load();
  document.fonts.add(face);
  refreshDocxFontMetrics();

  return () => {
    document.fonts.delete(face);
    refreshDocxFontMetrics();
  };
}

Provide matching font data and descriptors for each required weight and style. If a reference renderer uses different vertical metrics for the same font, the caller can supply verified ascentOverride, descentOverride, and lineGapOverride descriptors on the FontFace. These affect browser layout as well as viewer measurements. Bind such a profile to the actual font bytes and reference environment; a family name or document charset alone does not establish the correct metrics. The caller owns loading and cleanup. Load fonts before creating a standalone createDocxThumbnailRenderer; after changing fonts, dispose and recreate that renderer to refresh its captured pagination.

Thumbnail Hook

The library can expose page thumbnails so you can build your own page strip, mini-map, or navigation UI. Thumbnail painting can render from the live page surface when it is mounted, or from an offscreen one-page surface when viewer virtualization has unmounted that page.

import * as React from "react";
import {
  DocxEditorViewer,
  useDocxEditor,
  useDocxPageThumbnails,
} from "@extend-ai/react-docx";

export function ThumbnailExample() {
  const editor = useDocxEditor();
  const { thumbnails } = useDocxPageThumbnails(editor, {
    maxWidthPx: 160,
    maxHeightPx: 220,
    pixelRatio: 2,
    minRasterIntervalMs: 40,
    pageIndexes: [0, 1, 2, 3, 4, 5],
    renderWindow: {
      visiblePageIndexes: [0, 1, 2],
      prefetchPageIndexes: [3, 4, 5],
    },
  });

  return (
    <div style={{ display: "grid", gridTemplateColumns: "180px 1fr", gap: 16 }}>
      <div style={{ display: "grid", gap: 12, alignContent: "start" }}>
        {thumbnails.map((thumbnail) => (
          <canvas
            key={thumbnail.pageIndex}
            ref={thumbnail.canvasRef}
            width={thumbnail.pixelWidthPx}
            height={thumbnail.pixelHeightPx}
            style={{
              width: thumbnail.widthPx,
              height: thumbnail.heightPx,
              border: "1px solid #ddd",
            }}
          />
        ))}
      </div>

      <DocxEditorViewer editor={editor} />
    </div>
  );
}

Notes:

  • Thumbnail canvases can stay attached in a virtualized sidebar; only canvases you mount request paint work.
  • Use renderWindow.visiblePageIndexes for thumbnails currently visible in your sidebar, and renderWindow.prefetchPageIndexes to warm nearby pages after visible work.
  • Use pageIndexes as a hard render set when unlisted pages must remain metadata-only. It is distinct from the priority hints in renderWindow.
  • Call thumbnail.renderToCanvas(canvas, { scheduling: "immediate" }) for a visible cover that must bypass idle scheduling.
  • minRasterIntervalMs controls repeat renders for the same canvas. Lower values are useful when the consumer already limits thumbnail work to a small visible window.
  • Thumbnail sizing is bounded by maxWidthPx and maxHeightPx, so downstream UIs can bias toward portrait thumbnail rails.
  • Thumbnails use a direct layout/model canvas renderer first; if a page has no usable snapshot, the hook falls back to an isolated offscreen page surface.

Useful Hooks

@extend-ai/react-docx exports several hooks for wiring custom UI around the viewer:

  • useDocxDocumentTheme(editor) for light/dark document mode
  • useDocxPagination(editor) for current page and total page count
  • useDocxPageLayout(editor) for page size, margins, columns, and viewport defaults
  • useDocxPageThumbnails(editor, options) for rendering page previews into your own canvases
  • useDocxParagraphStyles(editor) for available paragraph styles and style selection
  • useDocxLineSpacing(editor) for selected line spacing state
  • useDocxBorders(editor) for paragraph/table border presets
  • useDocxFormFields(editor) for DOCX form-field state and updates
  • useDocxTrackChanges(editor) for tracked-change state plus safe acceptChange / rejectChange actions
  • useDocxComments(editor) for comment state, selected-range comment creation, and resolve/reopen actions
  • useDocxImageWrapMenu(editor) for image wrapping controls

Annotation mutations intentionally fail closed. Accept/reject currently supports a single top-level, text-only w:ins or w:del; comment creation supports one non-empty plain-text range in a top-level paragraph. Moves, formatting revisions, fields, hyperlinks, tables, nested revisions, drawings, and cross-paragraph ranges return an unsupported, stale, or unsafe-xml result without changing the document. Successful actions participate in the editor's normal undo/redo history. Pass the current DocxTrackedChange or DocxComment object back to its action; handles are document- and source-scoped, so a handle retained across an import or conflicting edit returns stale instead of targeting a reused OOXML id.

Lower-level APIs

The package also re-exports the lower-level internals used by the viewer:

  • OOXML parsing from @extend-ai/react-docx-ooxml-core
  • Document model helpers from @extend-ai/react-docx-doc-model
  • Editing operations from @extend-ai/react-docx-editor-ops
  • Layout primitives from @extend-ai/react-docx-layout-engine
  • Layout/core snapshot helpers from @extend-ai/react-docx-layout-core
  • DOCX serialization from @extend-ai/react-docx-serializer

This means you can build your own pipeline, for example:

import { buildDocModel, parseDocx, serializeDocx } from "@extend-ai/react-docx";

const pkg = await parseDocx(arrayBuffer);
const model = buildDocModel(pkg);
const output = serializeDocx(model, pkg);

Viewer Notes

  • DocxEditorViewer supports mode="edit" and mode="read-only".
  • pageBackgroundColor controls the page surface color.
  • pageGapBackgroundColor controls the area between pages and defaults to transparent.
  • In read-only dark document mode, the viewer uses an inversion-based night reader path so document content inverts cleanly while images keep their hues.

License

See the repository license for usage terms.