Overview — @helixui/document
@helixui/document is a sibling of @helixui/slides. It lets you build Word
documents the same way you build the rest of your UI — as React components.
The mental model is simple:
A
<Document>is a flow of blocks (<Heading>,<Paragraph>,<DocList>,<DocTable>, …). Page geometry is in inches, fonts are in points — the same units Word uses internally — so the DOM preview maps 1:1 to what lands in the.docx.
Live demo: /showcase/document — flip between
preset documents and click Download .docx. Open the file in Word, Google
Docs, or Pages and it’ll be a real, editable document.
Install
pnpm add @helixui/document
# Optional — only needed when you actually call the exporter.pnpm add docxThe docx library is a lazy peer dependency. The base @helixui/document
bundle does not import it until you call exportToDocx(). If you only want
the web preview, you don’t need to install it at all.
A 30-second document
import { Document, Heading, Paragraph, DocText, DocList, DocListItem, DocTable, exportToDocx,} from '@helixui/document';import '@helixui/document/styles.css';
function Brief() { return ( <Document exportFileName="brief.docx" meta={{ title: 'Q3 product brief', author: 'helixui' }} > <Heading level={1}>Q3 product brief</Heading> <Paragraph> We focus on three bets:{' '} <DocText weight="bold">tokens</DocText>,{' '} <DocText weight="bold">docs</DocText>, and{' '} <DocText weight="bold">interop</DocText>. </Paragraph>
<Heading level={2}>Bets</Heading> <DocList type="check"> <DocListItem>Ship a token manifest LLMs can consume.</DocListItem> <DocListItem>Render the same source to web, PPTX, DOCX.</DocListItem> <DocListItem>Stay framework-light on the runtime.</DocListItem> </DocList>
<DocTable header={['Bet', 'Owner', 'Status']} rows={[ ['Tokens', 'design', 'shipped'], ['Docs', 'docs guild', 'in flight'], ['Interop', 'core', 'planning'], ]} /> </Document> );}
// in a button handlerawait exportToDocx(<Brief />); // → brief.docxHow it maps to DOCX
Every primitive corresponds 1:1 to a docx construct so the export is predictable and lossless within the supported feature set.
| Component | DOCX equivalent | Notes |
|---|---|---|
<Document> | Document + first Section | Page setup, theme, file metadata |
<DocSection> | New Section | Mid-document page-setup change |
<Heading level={1..6}> | Paragraph + HeadingLevel.HEADING_n | Mapped to Word heading styles |
<Paragraph> | Paragraph with TextRuns | Block of text, inline formatting via children |
<DocText> | TextRun | Inline run with bold / italic / color / font / etc. |
<DocLink> | ExternalHyperlink + nested TextRun | Honors styling on the hyperlink runs |
<DocList> + <DocListItem> | Paragraph per item with bullet prefix | Manual bullet glyph for max compatibility |
<DocTable> | Table + TableRow + TableCell | Header row, optional zebra, optional borders |
<DocImage> | Paragraph with ImageRun | Fetched + base64-inlined; SVG → SVG with PNG fallback |
<DocCodeBlock> | Paragraph per line, mono font, shaded | Preserves whitespace and newlines |
<DocBlockquote> | Paragraph with left border + indent | Optional <cite> paragraph below |
<DocHorizontalRule> | Paragraph with bottom border | Standard thematic break |
<DocPageBreak> | Paragraph containing PageBreak | Forces a new page mid-flow |
Why points + inches?
Word measures fonts in points and page geometry in inches (or cm —
they’re equivalent). @helixui/document mirrors these units verbatim so
there’s no abstraction layer between props and the file:
- No coordinate translation. A
ParagraphwithspaceAfter: 6writes 6 points after the paragraph in the file. A column withcolWidths: [3.6]occupies 3.6 inches. - Consistent rendering. The DOM preview applies inches directly (CSS’s
inunit) and usestransform: scale()to fit the page canvas in any container. A 1-inch margin is always 1 inch. - Print-ready math. Page sizes match the standards: Letter → 8.5 × 11 in, A4 → 8.27 × 11.69 in, Legal → 8.5 × 14 in.
Sections vs the top-level Document
The <Document> component is itself a section — its pageSize,
orientation, and margins apply to the first chunk of content. To switch
page setup mid-document (e.g., a landscape spread inside a portrait
report), drop a <DocSection>:
<Document pageSize="letter"> <Heading level={1}>Cover</Heading> <Paragraph>…</Paragraph>
<DocSection orientation="landscape" margins={{ top: 0.5, right: 0.5, bottom: 0.5, left: 0.5 }}> <Heading level={2}>Wide diagram</Heading> <DocImage src="/architecture.svg" w={10} h={6} /> </DocSection>
<DocSection> <Paragraph>Back to portrait.</Paragraph> </DocSection></Document>Each <DocSection> becomes a new Section in the exported file and starts
on its own page by default (pageBreakBefore defaults to true).
Theming
<Document> accepts a theme prop with colors, fonts, and default font
sizes. The defaults pull values from helixui CSS variables, so any DNA theme
flows through. During DOCX export, any token reference ('brand',
'--helixui-color-...', oklch(...), etc.) is resolved to a concrete hex via
a hidden canvas — documents export faithfully under any theme.
<Document theme={{ colors: { brand: 'oklch(0.62 0.18 264)', ink: '#0b0d12', paper: '#fafafa' }, fonts: { heading: '"Geist", system-ui, sans-serif' }, bodyFontSize: 12, headingFontSize: 32, }}> …</Document>What’s next
- Components — every primitive at a glance
- Authoring guide — patterns for real documents
- Exporting — async export, file metadata, blob output, fonts, image inlining