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
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.