The DOCX Viewer renders a Word document with docx-preview directly — faithful, paginated pages (page size, margins, headers and footers), continuous scroll, zoom, fit-to-width, and download. It mirrors the PDF Viewer so the two are drop-in siblings.
It is built without manual page bookkeeping: the document blob loads through
React's use() with Suspense, docx-preview lays out the pages once, and
off-screen pages are skipped by the browser via CSS content-visibility — so a
long document only lays out and paints the pages near the viewport.
Installation
pnpm dlx shadcn@latest add @retab/docx-viewer
This pulls in docx-preview (and its single dependency, jszip). Everything is
resolved from the installed package by your bundler — there is no runtime CDN
call.
Usage
import { DocxViewer } from "@/components/ui/docx-viewer"
export function Example() {
return <DocxViewer src="/document.docx" className="h-[600px]" />
}Performance
docx-preview renders the document to the DOM (not a canvas), which is already
cheap. On top of that, each page section gets content-visibility: auto with a
matching contain-intrinsic-size, so the browser skips layout and paint for
pages outside the viewport while keeping the scrollbar stable. Zoom is applied
with CSS zoom on the laid-out pages, so changing scale never re-parses the
document.
Props
| Prop | Type | Description |
|---|---|---|
src | string | URL of the .docx (same-origin or CORS-enabled). |
scale | number | Optional fixed zoom; omit to fit page width to the container. |
toolbar | boolean | Show the zoom/download toolbar. Defaults to true. |
downloadFileName | string | Filename for the download button. |
onVisiblePageChange | (page: number) => void | Fired with the 1-based page nearest the top of the viewport. |
onScrollProgressChange | (progress: number) => void | Fired with scroll progress in [0, 1]. |
header | ReactNode | Strip rendered directly below the toolbar. |
aside | ReactNode | Left rail rendered alongside the scrolling pages. |
bare | boolean | Drop the outer border/background so the viewer fills its container. |
className | string | Optional class on the viewer shell. |