MostlyPDF

What actually survives HTML to PDF, measured

Twelve print-CSS features rendered through headless Chromium and then read back out of the PDF — page breaks, @page sizes, repeating table headers, links and fonts. Two of them do not work, and one common gotcha is the API's fault, not the CSS.

Max Čelar··HTML to PDF

What’s new here: A per-feature support matrix for HTML-to-PDF print CSS, measured on Chromium 148 by rendering each case and inspecting the resulting PDF's page count, page dimensions, link annotations and per-page text — not by reading documentation. Every verdict is reproducible from the published harness and dataset, and two widely-recommended techniques are shown not to work.

Every HTML-to-PDF service says its output is faithful. None of them publish what that means. So we measured it: twelve print-CSS features, each rendered through headless Chromium — the engine behind MostlyRender and behind most HTML-to-PDF services — and then read back out of the resulting PDF.

Nothing here is quoted from documentation. Each verdict comes from inspecting the file: how many pages it has, what size they are, which text landed on which page, whether a link annotation exists, whether a font was embedded.

# The matrix

Measured on Chromium 148.0.7778.96, first on 1 September 2026 and again on 27 September 2026. Both runs produced the same twelve verdicts.

Feature Works What we observed
break-after: page Yes 2 pages, as asked
page-break-after: always (legacy) Yes 2 pages — the old spelling is still honoured
break-inside: avoid Yes The block moved whole to page 2 instead of splitting
@page { size: A5 } Yes 420×595pt — exactly A5
@page { size: A4 landscape } Yes 842×595pt — landscape
Repeating <thead> across pages Yes 3 pages, header present on page 2
position: fixed as a running header Yes Present on 2 of 2 pages
@page margin boxes + counter(page) No No page number anywhere in the output
<a href> as a clickable link Yes Link annotation present
Internal #anchor links Yes Destination present
Font embedding Yes DejaVuSansMono embedded and subset
CSS grid across a page break No All 8 cells on page 1; page 2 empty

Ten of twelve work. The two that do not are worth knowing about before you design around them.

# The two that fail

CSS Paged Media margin boxes do not render. The spec gives you @page { @bottom-center { content: "Page " counter(page) } }, it is in every tutorial about printing from the web, and Chromium ignores it. We found no page number in the output at all. If you need page numbers, they have to be added by whatever produces the PDF — which is why tools that offer page numbering do it as a feature rather than telling you to write CSS.

CSS grid does not fragment. We laid out eight items in a two-column grid tall enough to need two pages. All eight rendered on page one, and page two came out blank. Grid is fine for content that fits on a single page; for anything that paginates, use block flow or a table.

# The gotcha that is not CSS at all

While building the harness we recorded @page { size: A5 } as unsupported — and it was our own mistake. The renderer was being told to use A4 by the API call, and a paper format passed by the caller silently overrides the document’s @page size. With the format omitted and CSS page size preferred, A5 and landscape both came out exactly right.

This is worth stating plainly because it is almost certainly the most common reason people conclude @page is broken: the CSS is fine, and something upstream is quietly outranking it. We only caught it because a result that disagreed with the spec made us re-read the harness rather than publish the number.

# Reproducing this

The harness and the dataset behind the table are both published:

  • bench-htmltopdf.js launches Chromium, renders each case, and inspects the output with pdf-lib and pdf.js.
  • html-to-pdf.json is the output we wrote this table from, stamped with the Chromium version it measured.

To run it yourself, save the harness into an empty folder and run:

text
npm i playwright pdf-lib pdfjs-dist@3
npx playwright install chromium
node bench-htmltopdf.js

One limit worth stating: font detection scans the raw PDF bytes for font names, so a positive result is solid but an empty one only means “not visible to this method”. The harness reports that case as inconclusive rather than as a failure, because publishing “fonts are not embedded” on the strength of a scan that cannot see them would be exactly the kind of claim this article exists to avoid.

# Why we measured it

We build an HTML to PDF tool and API on this engine. Publishing what it does and does not do is more useful than asserting that it is faithful — and if a number here is wrong, the harness that produced it is linked above to be run.

Frequently asked questions

Does page-break-after still work, or do I need break-after?

Both work. We measured the modern break-after: page and the legacy page-break-after: always separately and each produced the expected two-page document on Chromium 148. If you have older templates emitting the legacy spelling, they are not the reason your pagination is wrong.

Why is my @page size being ignored?

Almost always because the caller overrode it. If the API or library that drives the render passes a paper format of its own, that wins and your @page { size: … } is discarded silently. With the format omitted and CSS page size preferred, A5 and landscape both came out exactly as asked. This is the single most common false report of "@page size does not work" — it caught us while building the harness for this article.

Can I put page numbers in the margin with CSS?

Not in Chromium. @page { @bottom-center { content: counter(page) } } is part of CSS Paged Media and is widely documented, but the margin box simply does not render — we measured it and found no page number anywhere in the output. Page numbers have to come from the tool doing the conversion, not from your stylesheet.

Do CSS grid layouts break across pages properly?

No. In our test, all eight grid items rendered on page one and the second page came out empty — the grid did not fragment. If your document is long enough to paginate, lay the repeating part out with normal block flow or a table and keep grid for things that fit on one page.

Are hyperlinks preserved in the PDF?

Yes. Both an external <a href> and an internal #anchor produced real link annotations in the output file, so links stay clickable and a generated table of contents stays navigable.