Skip to content

CSS page breaks for HTML to PDF

The print CSS that makes multi-page invoices and statements look right: page size and margins, table headers on every page, and rows and totals that never split.

By Saurav N, senior software engineer · Updated

· 5 min read

Set the page with @page

@page sets the paper size and margins for every page. Chrome, and so Puppeteer, Playwright and Templater, all honour it. Keep the bottom margin large enough for a footer with page numbers.

Tables that span pages

thead { display: table-header-group } repeats the header row at the top of each page. tr { break-inside: avoid } stops a row from being cut in half. Both matter most for long line-item tables.

Keep things together, or force a new page

break-inside: avoid on the totals block keeps it in one piece. break-before: page starts a section, such as terms or an annexure, on a fresh page. break-after: avoid on headings stops them being stranded at the bottom of a page.

print.css
@page {
  size: A4;
  margin: 16mm 14mm 20mm;
}

/* Repeat the table header on every page, keep a row in one piece. */
thead { display: table-header-group; }
tr    { break-inside: avoid; }

/* Keep the totals block with the last rows, and start annexures on a new page. */
.totals   { break-inside: avoid; }
.annexure { break-before: page; }

/* Headings never sit alone at the bottom of a page. */
h2, h3 { break-after: avoid; }

/* Print the colours you designed. */
body { print-color-adjust: exact; -webkit-print-color-adjust: exact; }

Page numbers

Chrome fills <span class="pageNumber"> and <span class="totalPages"> in a PDF footer. In a Templater PDF template, write {{pageNumber}} and {{totalPages}} in the footer and they become those spans.

Questions

Do the older page-break-* properties still work?

Yes. page-break-before, page-break-after and page-break-inside are aliases of the break-* properties, and Chrome supports both.

Why does break-inside: avoid not work on my div?

It applies to block-level boxes. Inside flex or grid containers Chrome often ignores it; wrap the part in a normal block element.

Why are my background colours missing?

Browsers skip backgrounds when printing unless print-color-adjust: exact is set, or printBackground is on in Puppeteer.

Try it on your own template

Start Free