Skip to main content
Forme 0.14 adds a second front door to the engine: @formepdf/html renders HTML + print CSS to PDF through the same Rust/WASM engine that renders JSX components. No headless browser, no Chromium cold start.

The subset — the constitution

This is a deliberately documented subset of HTML/CSS, in the spirit of Satori — but page-native. Everything supported is listed below and tested. Everything outside the subset lands in the warnings list at render time, named, with a remedy where one exists. Nothing fails silently.

Paged media — the point of the whole thing

Elements

Selectors

Properties

CSS Grid is supported as a documented subset (not all of grid): fr/px/auto/minmax tracks, integer repeat() — including Tailwind’s repeat(N, minmax(0, 1fr)) — gaps, spans, and numeric line placement; named lines, areas, auto-flow, auto-fill/fit, percentage tracks, and negative line numbers warn by name. The full property table lives in the package README — margins with CSS margin collapsing, borders (solid/dashed/dotted with Chrome-matched dash metrics), border-collapse emulation, min/max width constraints (the centered-column idiom works), font fallback chains, text-align: justify with real Knuth-Plass distribution, text-transform, letter-spacing, vertical-align on cells, flexbox, relative/absolute positioning, floats as column rows (float: left/right + clear — consecutive floated siblings form a row, the Bootstrap col-* / left-right header-pair shape; text wrapping alongside a float is not supported and warns — following content renders below), CSS tables on divs (display: table/table-row/table-cell — the equal-height-columns idiom; a single-row one becomes a breakable column row), position: fixed as position: absolute by policy (anchored on the page where it occurs, not repeated per page — margin boxes are the running-content mechanism), and body { overflow-x: hidden } as a page-level clip (how off-viewport-parked sidebars stay invisible). Notable exclusions, all warned by name: CSS variables, position: sticky, transforms, gradients, @font-face fetching (use options.fonts / --font — see the web-fonts migration recipe).

The warnings contract

Every warning names the thing it skipped: the property, the selector, the stylesheet href, the @font-face family. If a render is silent, the document used only the subset — and what you got is what the subset defines.

Browser, Workers, and edge

The HTML path ships web-target builds alongside Node (since 0.15.0), verified in real headless Chromium and real workerd:
Sizing: the HTML engine WASM is 7.66 MB uncompressed, 3.41 MB gzipped (measured from the published 0.20.0 package; the CI-measured figure lives in the parity artifact). Size is no longer the Workers constraint — Cloudflare raised the limit to 64 MiB uncompressed on all plans (2026-09-04), so the module fits the free plan. The real constraint is CPU time: the free plan caps at 10 ms per request and a typical render is ~20 ms (measured in workerd, local Apple Silicon, 0.20.0), so real workloads need the paid plan. The module compiles and instantiates in ~1 ms in a Workers isolate, so its size is a disk/transfer cost, not a cold-start cost. Mind the Workers 128MB memory ceiling, though: table-heavy documents render comfortably up to ~50 pages and approach the limit near ~100 pages (details).

Migrating