@formepdf/html
HTML + print-CSS to PDF on the Forme engine. No headless browser, no Chromium cold start — a Rust layout engine compiled to WASM, running wherever Node runs.
npx @formepdf/html invoice.html
const { renderHtml } = require('@formepdf/html');
const { pdf, warnings } = renderHtml(html, { pageSize: 'Letter' });
fs.writeFileSync('invoice.pdf', pdf);
warnings.forEach((w) => console.warn(w)); // everything outside the subset, named
What works
The paged-media CSS browsers took two decades to ship — and the parts they still haven't:
@page: size (named or dimensions), margins,:firstvariants- Margin boxes (
@top-center,@bottom-right, ...): running headers/footers in the page margin, withcontent()strings andcounter(page)/counter(pages) - Break control:
break-before/after/insideplus the legacypage-break-*spellings every wkhtmltopdf-era template still carries orphans/widows,<thead>repetition on every page- Stylesheets with a documented selector subset, the full cascade with
!important, flexbox, a CSS Grid subset, tables with colspan/rowspan, lists, images - Floats as column rows —
float: left/right+clearlay consecutive floated siblings out as a row (the Bootstrapcol-*and left/right header-pair shape real templates use); only true text-wrapping alongside a float is out, and it warns
Measured against 15 real production templates from GitHub (Bootstrap
2/3 grids, mPDF/wkhtmltopdf-era tables, email HTML, modern CSS grid):
14 of 15 render correctly, the one degraded case is legible with its
cause named in warnings, zero render broken (as of 0.20.0).
The complete property-by-property subset table lives in the
repository README —
everything outside it lands in warnings, never silently dropped.
CLI
forme-html <input.html> [-o out.pdf] [--css print.css]
[--page-size Letter] [--margin 36] [-q]
CLI/API options override the document's @page rule, the way a print
dialog overrides a stylesheet.