Skip to content

CSS for print: page breaks, repeating table headers and @page margins

, 5 min read

Browsers print HTML with the same engine that draws it on screen, plus a handful of print-only CSS features. Knowing those few features is most of the difference between a PDF that looks designed and one that breaks a table in half. Everything here works in Chromium, which is what pagenull and Puppeteer use.

Page size and margins with @page

print.css
@page {
  size: A4;            /* or Letter, or 210mm 297mm, or A4 landscape */
  margin: 20mm 16mm;
}
@page :first {
  margin-top: 30mm;    /* more room on the first page only */
}

With pagenull, preferCssPageSize is on by default, so a size in @page wins over the format option. Set margins in one place, either @page or the API’s margin option, so you never have to work out which one applies.

Page breaks

print.css
.chapter      { break-before: page; }   /* always start on a new page */
.card, figure { break-inside: avoid; }  /* never split across pages */
h2, h3        { break-after: avoid; }   /* keep a heading with what follows */
p             { orphans: 2; widows: 2; }

The older names (page-break-before, page-break-inside) still work. break-inside: avoid is a request, not a guarantee: an element taller than a page has to break somewhere.

Tables that span pages

Put the header row in <thead> and it repeats at the top of every page; <tfoot> repeats at the bottom. Keep rows whole with break-inside: avoid on tr.

invoice.html
<table>
  <thead><tr><th>Description</th><th>Qty</th><th>Amount</th></tr></thead>
  <tbody>
    <tr><td>API integration, October</td><td>12 h</td><td>€1,020.00</td></tr>
    <!-- … hundreds of rows … -->
  </tbody>
</table>

pagenull injects a small print reset before your own CSS, so these are already on unless you set printReset: false: repeating thead/tfoot, no split rows, images and code blocks, headings kept with the next element, orphans and widows of 2, exact colours, images no wider than the page, and two helper classes, .page-break and .no-break. Your own rules override all of it.

Backgrounds and colours

Browsers drop backgrounds when printing unless told otherwise. print-color-adjust: exact on the element (part of the reset) and printBackground (on by default) keep table stripes, badges and brand colours.

Page numbers, headers and footers

For “Page 3 of 12”, set pageNumbers: true or a template such as "{page} / {pages}", and pageNumbersPosition. For a full header or footer, pass headerHtml and footerHtml; the details and the classes Chromium fills in are in headers, footers and page numbers.

Print styles vs screen styles

The renderer uses print media by default, so @media print rules apply. If your page only has screen styles and looks wrong in print, try emulateMedia: "screen" first.

Fonts

Load web fonts with <link> or @font-face; with JavaScript on, the renderer waits for fonts to load before printing. If a font fails, the response’s pagenull-warnings header names the problem, such as missing glyphs.

Checking a layout quickly

Paste HTML into the playground, or send "test": true renders while you work: they are watermarked, free and not counted.

Terminal
curl https://api.pagenull.dev/v1/pdf -H "Authorization: Bearer $PAGENULL_API_KEY" -H "Content-Type: application/json" \
  -o test.pdf -d '{"html":"<style>@page{size:A5;margin:12mm}</style><h1>Hello</h1>","test":true}'