Renderpaper
Log in Start free

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:

  1. A row is cut in half by the page boundary — the description on page one, the amount on page two.
  2. 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.

Last updated 2026-08-08.