A Ristmark template is a tree of typed blocks — text, tables, layout containers, form fields, and more — each with its own config, laid out by the same deterministic engine that renders your final PDF, HTML or DOCX. This page documents every block type registered in the engine, authored directly from the block cores in libs/doc-engine/src/blocks/.
Every block shares a common frame regardless of type: a position (flow, stacked top-to-bottom automatically, or anchored, pinned to an exact spot), optional margin/padding/fill/border/corner-radius, an optional visibility condition, and an optional {{ }}-bound content. What’s documented below is each block’s own config — the fields specific to that block type.
The base text block: a single paragraph in one style throughout, wrapped and measured with real per-character font metrics. Use it for any standalone line or paragraph of copy — a heading, a label, a sentence.
text — the content string; supports {{ }} data bindings, interpolated on render.fontSizePt (default 11), fontFamily (default the sans family), weight (normal/bold), italic, color (default #000000), align (left/center/right/justify), lineHeightMul (line-height multiplier, default 1.2).A text block is always one style throughout. For a paragraph that mixes bold/italic/colored/linked spans in one line, use rich-text below instead — a migrated document’s mixed-style paragraphs arrive as rich-text blocks automatically.
Wraps on real word boundaries and splits cleanly across a page break — a {{ }} binding is never cut mid-expression, and each half is resolved independently so page-scoped tokens (like the page-number block’s $page.*) stay correct on both sides of the break.
A general-purpose body-copy block. As authored today it also accepts a markdown field: when non-blank, it renders through the same Markdown engine as the dedicated markdown block below (headings, bold/italic, links, lists — real styled runs). text/html remain for templates authored before that switch — html is flattened to plain text (block tags become newlines, everything else stripped) rather than rendered as real HTML.
markdown — Markdown source; wins over text/html when set.text — plain text; wins over html when set.html — legacy HTML, flattened to plain text.fontSizePt (default 11), fontFamily, weight, italic, color (default #000000), align (default left), lineHeightMul.Splittable across pages: non-Markdown content splits like text; Markdown content splits between top-level Markdown blocks.
A dedicated Markdown-authoring block for multi-block, structured content — headings, paragraphs, lists, horizontal rules — with real per-run formatting, rather than rich-text’s single flattened paragraph.
source (required) — the Markdown source; may contain {{ }} bindings.fontSizePt (default 11, base size — headings scale from it), fontFamily, color (default #000000), align (default left), lineHeightMul.A bound value is interpolated into the source before parsing, so data-supplied structure renders as real structure (a value containing **hi** renders bold, not literal asterisks) — the one block where this is true. Every other text-bearing block, including rich-text above, resolves a binding after parsing, so a bound value can never inject structure there. Supports headings, paragraphs, list items, horizontal rules, inline code, bold/italic/underline and links; any construct it doesn’t understand renders as a loud placeholder rather than being silently dropped. Splits between top-level blocks only, never mid-paragraph.
A bulleted or numbered list, one line per item, splittable by whole item across pages.
items — static item strings (each may carry {{ }} bindings). Used when dataPath is absent.dataPath — a path to an array in the render data; when set, wins over items and repeats itemTemplate per row (each row’s own fields bind directly, plus the whole row as {{ item.* }}).itemTemplate — the per-row template for the dataPath case, e.g. {{ item.name }}.ordered (numbered vs. bulleted), gapMm (vertical gap between items, default 1), fontSizePt (default 11), weight, color, markerColor (falls back to color).nestedItems — a static, explicitly multi-level list ({ text, level }[]), with per-depth marker formats (decimal/alphabetic/roman/bullet glyphs) via levelFormats. Wins over both items and dataPath when set. Nested lists are not splittable — an over-tall nested list places whole on one page.A dataPath that resolves to nothing produces a visible diagnostic rather than silently rendering no items.
One horizontal band of side-by-side items (columns), each holding its own nested child blocks — the tool for putting content side by side (a logo next to an address block, several summary cards in a row). Splits in pagination: every column’s own content tears at the same y-boundary, so the row fills the current page and continues on the next; a column with nothing splittable at that boundary defers its whole content to the next page instead.
items — an array of { width?, valign?, blocks }. Each item’s width is { kind: "weight", value } (proportional share), { kind: "mm", value } (fixed), { kind: "auto" }, or { kind: "token", id } (resolved against the template’s width tokens); items with fixed/token widths are carved out first, the rest share what’s left by weight. valign is top/middle/bottom (baseline falls back to top).gapMm — gap between items (default 4).minHeightMm — a floor under the row’s natural height.A child block’s own keepWithNext flag pulls it into the same page as the block that follows it, within its own column.
A vertical flow of child blocks — like layout-row turned 90° — that, unlike a plain container, splits between children by default when it doesn’t fit the remaining page.
blocks — the stacked children.gapMm — gap between children (default 4).split — "between-children" (default: fits as many whole children as it can onto the current page, and can even split the one overflowing child if that child itself supports splitting) or "atomic" (always moves whole, like a plain container).A child block’s own keepWithNext flag pulls it into the same page as the block that follows it (e.g. never break between a heading and its section) — honoured by layout-stack, layout-row (within a column), and sheet (within a region) alike.
A label + value “molecule” — one block replacing what would otherwise be several hand-positioned blocks (label, value, a rule, and their alignment). Use it for form-style fields: Invoice #: 12345, Payer: {{ $data.payerName }}. Atomic in pagination — no split, same as layout-row.
label — the label text.value — the value text/expression; {{ $data.* }} bindings resolve like a text block.arrangement — "inline" (label beside value, default) or "stacked" (label above value, form-field look).labelWidth — inline-only column width (same weight/mm/auto/token model as layout-row); absent = equal split.valueAlign — the value’s own text alignment (the label is always left-aligned).rule — an optional owned hairline: { under: "value" | "all", stroke? } — under just the value column, or the full pair width.labelStyle / valueStyle — independent font/weight/italic/color overrides for each half (neither falls back to the other).A ruled-skeleton container: a bounded mini-canvas whose skeleton is a set of draggable horizontal/vertical rules, with content-holding regions in the spaces between them. Built for ruled/positioned document design — form headers, label/value rows sitting on hairlines, three-sided boxes, signature zones — reconstructed from PDF/image sources or authored by hand.
rules — each { id, axis: "h"|"v", posMm, from, to, stroke? }. from/to anchor a rule to the sheet edge, an absolute mm position, or a perpendicular rule’s id. stroke absent/null = an invisible guide (never painted); present = a painted line.regions — each { id, edges: {top,right,bottom,left}, blocks, padMm?, valign?, grow?, role?, binding?, confidence?, group? }. edges bind a region to rule ids (or an absolute mm). blocks are ordinary child blocks laid out top-to-bottom inside the region. grow is "push" (default: overflowing content pushes the bound bottom rule — and everything below it — down) or "fixed" (never pushes; content can extend past the box rather than being clipped).heightMode — "auto" (default: height derives from the rules/regions, push applies) or "fixed" (geometry exactly as authored, push never applies).A sheet cannot nest inside itself — a sheet authored inside another sheet’s region is stripped with a diagnostic. Splits at a full-span painted horizontal rule with no region straddling it; failing that, a single straddling region’s own content can still split at a synthetic (non-rule-aligned) cut. A child block’s own keepWithNext flag pulls it into the same page as the block that follows it, within its own region.
Renders a semantic table, from one of three mutually-exclusive sources, in priority order: a static merged grid of literal cells (cells, supports col/row-span and per-cell nested blocks — used for hand-authored or ingested tables); bound data (dataPath, one row per array item); or inline static rows.
columns — { header, width?, align?, key?, format? }[]. key picks the field out of each dataPath row; format (currency/date/upper/lower) applies to data cells only, never headers.rows — inline static string grid, used when dataPath is empty.dataPath — path to an array of row objects.cells — the static merged grid: each cell can carry colSpan/rowSpan, its own align/fill/fontSizePt/borders/valign/textStyle, and even its own child blocks (a “block-cell”, turning one cell into a mini-container).headerFill, zebraFill — header background and alternating-row background colors.border, gridLines ("grid"/"horizontal"/"outer"/"none", default "grid") — the default rule appearance; per-cell borders override on a side-by-side basis and always win.cellPaddingMm — per-cell padding, a number (uniform) or { x, y }.Splits across pages keeping the header row on every piece; for the cells grid path, a row-spanning cell group is never split across the break (row-span-atomic).
Renders a child block template once per row of a bound data array, stacking iterations top-to-bottom — the tool for a list of repeating content (invoice line items) built from nested child blocks rather than a table’s columns.
dataPath (required) — path to the bound array.itemTemplateBlocks (required) — child blocks laid out once per row; edited via a dedicated child editor, not a plain form field.gapMm — vertical gap between rows (default 2).itemAlias — the name the whole row is exposed under for child bindings (default "item"), alongside the row’s own fields binding directly and outer/document data staying reachable.Rows are capped at 3,000 — beyond that, extra rows are omitted with a visible placeholder and diagnostic, never silently truncated. Splits between whole rows only, never mid-row.
A labelled fill-in field or checkbox, for reconstructing form widgets (blanks, tick-boxes) using only text/line/rect primitives — no HTML <input> involved.
kind — "text-field" (a label + fill-in zone) or "checkbox" (a tick-box + label).label (may contain {{ }}), labelWidthMm (fixed label-column width; default 0 for a checkbox, 30mm for a text field).value (the filled-in value, empty = blank fill-in zone), fieldStyle ("underline" or "box", default underline).checked (a literal boolean — data-bound checked state isn’t supported yet), checkGlyph ("check" or "x", default check).fontSizePt (default 11), color, borderColor (box/checkbox border, default #000000).Places a raster image (logo, photo). The engine core carries a typed reference only — actual byte resolution happens at render/resolve time, edge-side.
assetId — an uploaded-asset reference; preferred over src when both are set.src — a URL/data-URL/{{ }} binding, used when there’s no assetId.fit — "contain" (default), "cover", or "fill" (mirrors CSS object-fit).align — "left"/"center"/"right", effective only when the authored box is narrower than the flow width.No cross-page split — an image always places as one whole block.
A free-standing horizontal rule, sized and positioned by whatever rect you draw for it on the canvas.
thicknessMm (default 0.4), color (default #000000), style (solid/dashed/dotted).lengthMode — { kind: "full" } (default) or { kind: "mm"|"percent", value } to draw a rule shorter than its box.align — left/center/right, positions a shortened rule within its box.The same rule-drawing core as line, but always spans the full content-band width — the block for a horizontal rule that separates sections edge-to-edge, with a small fixed height (2mm) if none was authored. Shares line’s exact config shape (thicknessMm/color/style/lengthMode/align).
A purely visual box — a filled and/or bordered (optionally rounded) rectangle, with no content of its own. Use it as a background panel or decorative frame behind other blocks.
fill — a CSS color, or null/absent for no fill.border — stroke color/width/style (default: 0.35mm solid black).radiusMm — corner radius (default 0, square).A QR code drawn as crisp vector output (its own IR primitive — never a raster image), encoded via a standard QR library.
value — the text/URL encoded (a literal string; data-binding support is a later phase).sizeMm — side length of the square code (default 25, clamped to the available width).ecLevel — error-correction level L/M/Q/H (default M; higher = denser code, more scan robustness).quietZone — light-border width in modules (default 4, the QR spec minimum).darkColor / lightColor — module colors (default black/white).An empty resolved value renders a loud placeholder, never a blank or invalid code.
A 1D barcode drawn as crisp vector bars, symbology fixed to Code39 (a hand-verifiable, checksum-free, per-character table — chosen so encoding correctness is provable rather than delegated to a black box).
value — the text encoded (uppercased; a character outside the Code39 set maps to a space rather than throwing).symbology — fixed "code39" for v1.heightMm — bar height (default 18).showText — render the human-readable value under the bars (default true).quietZone — light border in narrow modules at each end (default 10).lineColor / background — bar and background colors (default black/white).Same empty-value → loud-placeholder behavior as qr.
A flow block that reserves a fixed vertical gap and draws nothing — for adding breathing room between other blocks.
heightMm — the gap height (default 8).A zero-height marker with no config at all — its mere presence in the flow forces every block after it onto a new physical page. No visual output.
A single-line text block whose content is generated by interpolating a template string against the page context — built for a header/footer page-number line.
template — the interpolated format, default "{{ $page.number }} / {{ $page.count }}". Supports {{ $page.number }}, {{ $page.count }}, {{ $page.sectionNumber }}, {{ $page.sectionCount }}.fontSizePt (default 9), weight (default normal), align (default center), color (default #444444).