Escape
HomeDocsThe DOCX target

The DOCX target

Intent

@quario/docx resolves the same definition every other target reads into the bytes of a .docx. Real Word tables, inline pictures, and a grouping that becomes the navigation pane. It’s a document the reader edits in their own application, rather than a page you have already drawn for them.

Design decisions

It’s a flow target, not a painter. It states the geometry and the typography and lets Word paginate, so it runs no layout of its own. The promise is the same geometry and typography; where the pages break is the reader’s. That’s the deliberate difference from the PDF target, which paginates itself, and can therefore promise where every line falls. Choose by which promise you need: a document someone will edit, or a page that must fall the same way everywhere. Painters, flow targets, and a fragment sets out the family.

A table detail is a real Word table. Declared column width percentages are exact. The columns that declared none divide the remainder evenly, not from their content: a flow target measures nothing. That’s the one approximation here. A fixed layout stops a reader re-measuring and losing the shares. The header row repeats after every page break, and a span becomes a gridSpan across the columns it covers.

Grouping becomes the outline. A group header’s first item takes a heading style by depth, so the navigation pane mirrors the report’s own structure. Those heading styles exist for the outline rather than the look: Normal is empty. The report default and every authored declaration land as direct formatting on the runs and paragraphs that wear them. A look that leaned on a style lookup is a look three readers may resolve three different ways.

There is no stylesheet and no font option. A document is final when it opens, so what this target supplies where the author declared nothing, it supplies itself.

API walkthrough

Rendering the bytes

report.js
import { quario } from 'quario'
import { docx } from '@quario/docx'

const report = quario({ locale: 'en-IE', currency: 'EUR' }).report(definition)

const bytes = await report.render(
  docx({ page: { size: 'A4' }, meta: { title: 'Sales 2026' } }),
  data,
)

page is the PDF target’s, validated identically. size defaults to 'A4', an array is a custom [width, height] in points, and margin defaults to 54 points when the document declares none. meta writes document properties, and takes strings only: no date, so no clock reaches the package.

Page bands, and live fields

Page bands become a Word section’s header and footer parts. This target calls each band’s closure twice: once at page 1 of 2, and once at page 2 of 2. Equal results become one part. Results that differ become a first-page part and a default one, so the cover-page idioms visible: "=page.number > 1" and "== 1" resolve exactly.

A bare {{ page.number }} or {{ page.total }} becomes a live field the reader’s own application recomputes. Anything computed from them freezes at the value the probe read. A band gated on page.total therefore sees 2 — best-effort, exactly as the PDF target’s height probe is.

Breaking and restarting

A group’s break: "page" is a page break before the instance’s first paragraph, and reset: "page" opens a new section whose page numbering restarts at 1.

Determinism

The same schema, data and options produce byte-identical output on every supported runtime. Every zip entry carries the same 1980-01-01 stamp, no part carries a clock, and the document properties have no slot for one.

You state the look. Word turns the pages.

npm install quario
Getting started
© 2026 quario · KvK 61815977