Stop a table splitting badly across PDF pages
A long table paginated by a browser has two failure modes, and both look like bugs to whoever receives the document:
- A row is cut in half by the page boundary — the description on page one, the amount on page two.
- The header row appears once, so every page after the first is columns of numbers with nothing saying what they are.
Both are two declarations away.
thead { display: table-header-group; }
tr { break-inside: avoid; }
What each one does
display: table-header-group tells the renderer to repeat <thead> at the top of
every page the table continues onto. It is not a hint — it is how table pagination
is specified, and it is well supported.
break-inside: avoid on the row says the row is atomic: if it does not fit in the
remaining space, move the whole thing to the next page rather than splitting it.
<table>
<thead>
<tr><th>Description</th><th class="num">Qty</th><th class="num">Amount</th></tr>
</thead>
<tbody>
{{range .LineItems}}
<tr>
<td>{{.Description}}</td>
<td class="num">{{.Quantity}}</td>
<td class="num">{{.Amount}}</td>
</tr>
{{end}}
</tbody>
</table>
Verified 2026-08-08: 90 rows at A4 paginated to three pages, with the header row present at the top of all three and no row split across a boundary.
Do not put break-inside: avoid on the table
/* Wrong — this asks the renderer not to break the table at all. */
table { break-inside: avoid; }
A table taller than one page cannot honour that. Depending on the engine you get an enormous overflowing first page or the rule ignored entirely. The rule belongs on the row, which is the unit that should stay whole.
Keeping a group of rows together
For a subtotal that must not be orphaned from its section, wrap the group in a
<tbody> — a table can have several — and mark that:
tbody.group { break-inside: avoid; }
<tbody class="group">
<tr><td>Consulting</td><td class="num">12</td><td class="num">14 400.00</td></tr>
<tr><td>Hosting</td><td class="num">1</td><td class="num">4 350.00</td></tr>
<tr class="subtotal"><td>Subtotal</td><td></td><td class="num">18 750.00</td></tr>
</tbody>
Same caveat as above: if the group is taller than a page it cannot be honoured.
Forcing a break
.new-section { break-before: page; }
The older page-break-before: always still works and is what most search results
show. break-before: page is the current property and behaves identically in
Chromium; prefer it in new templates.
A repeating document header, not just a table header
table-header-group also solves the harder problem: a header that repeats on every
page and carries a logo, an address block, real structure. Wrap the whole document
in an outer table and put that content in its thead.
The full technique, with measurements and the position: fixed failure recorded so
nobody retries it, is in
repeat a header on every page.