Keeping a long order list readable across multiple pages, without tearing it apart
A long table breaking mid-row when printed is one of the most visible CSS bugs on invoices and order confirmations. With break-inside: avoid for rows and a correctly used thead as a repeating header, page breaks can be controlled deliberately so every page stays readable on its own, with no JavaScript or server-side PDF rendering required.
Table of Contents
- 1. The core problem: why tables tear apart when printed
- 2. break-inside: avoid for table rows
- 3. Repeating table headers across page breaks with thead
- 4. tfoot for totals rows: fixed to page end or only table end
- 5. Working together with @page: header and footer of the printed page
- 6. Common print errors on order confirmations and invoices
- 7. Testing page breaks without an actual printer
- 8. Limits of plain CSS versus server-side PDF export
- 9. A checklist for multi-page tables in print
- 10. Summary
- 11. FAQ
1. The core problem: why tables tear apart when printed
By default, browsers break a page exactly where the available space runs out, with no regard for whether that point falls in the middle of a table row. For an order list with many line items, that can mean, in the worst case, that item name, quantity and price of a single row end up split across two different pages, while the matching column headers stay visible only on the first page.
This behavior is not a browser bug but the plain default without explicit control: the print algorithm has no automatic knowledge of the logical relationship between the cells of a row, it only sees a continuous sequence of boxes that it breaks at the next best opportunity. Only deliberate CSS rules tell the browser which areas must stay together as a unit.
2. break-inside: avoid for table rows
The property break-inside: avoid, applied to tr, instructs the browser to avoid a page break inside a table row wherever possible, instead moving the entire row to the next page if it no longer fits completely on the current one. For the vast majority of rows with normal height, this works reliably across every modern browser and resolves the core problem of torn rows almost completely.
For unusually tall rows, for example with multi-line free text in one column, avoid can hit physical limits: if the row itself does not fit on a full page, the browser still has to break somewhere. In practice this rarely affects normal order line items, but for free-text fields like delivery notes, deliberately capping the row height in the print stylesheet is worth doing.
@media print {
table {
border-collapse: collapse;
width: 100%;
}
/* Keep rows from being torn apart mid-break */
tr {
break-inside: avoid;
}
/* Protect headings and footers as well */
h1, h2, h3, .invoice-header {
break-after: avoid;
}
}
3. Repeating table headers across page breaks with thead
A properly marked-up <thead> is automatically repeated by every mainstream browser on each new page a table continues onto, provided the table actually uses thead and tbody rather than plain tr elements sitting directly under table. This automatic repetition is not strictly a CSS property but built-in behavior of HTML table semantics combined with the browser's print model.
The most common reason this behavior fails in practice is missing or incorrectly structured markup: if the header row is only visually highlighted with font-weight: bold but technically marked up as a plain tr inside tbody, the browser does not recognize it as a repeatable header and only shows the column labels on the first page.
<table class="order-items">
<thead>
<tr>
<th>Item</th>
<th>Qty</th>
<th>Unit price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr><td>Product A</td><td>2</td><td>19.90 EUR</td><td>39.80 EUR</td></tr>
<tr><td>Product B</td><td>1</td><td>49.00 EUR</td><td>49.00 EUR</td></tr>
<!-- ... many more rows ... -->
</tbody>
</table>
4. tfoot for totals rows: fixed to page end or only table end
Besides thead, browsers also support <tfoot> as a repeatable footer area, but with one important difference: unlike the table header, the footer by default only appears at the actual end of the entire table, not on every intermediate page. For a running subtotal that is meant to appear at the bottom of every page, tfoot alone is not enough.
For that pattern, common on multi-page invoices with a running subtotal, you need either a combination of several deliberately repeated rows within the content area itself, or a server-side split of the table into several smaller tables each with its own subtotal, because plain CSS has no mechanism for page-relative running totals.
5. Working together with @page: header and footer of the printed page
While thead repeats the table header within the content flow, @page rules with their margin boxes control the actual header and footer of the printed page itself, such as a company logo, page number, or invoice number meant to appear on every page independently of the table content. The two mechanisms complement each other: thead for the domain-specific table header, @page margin boxes for the document-wide page frame.
An order confirmation with many line items typically benefits from both at once: the margin box at the top of the page shows the company logo and order number on every page, while the repeated thead directly above the table supplies the column labels for the continued item list. That keeps every printed page readable without losing context, even when viewed in isolation.
@page {
size: A4;
margin: 2cm 1.5cm;
@top-right {
content: "Order confirmation " counter(page) " / " counter(pages);
}
}
6. Common print errors on order confirmations and invoices
By far the most common mistake is a table whose header row is technically a plain tr inside tbody, so the column labels only show up on page one and every following page sits without context. The second most common mistake is a missing break-inside: avoid, which lets individual line item rows get sliced right through quantity and price, which on an invoice quickly looks like a calculation error rather than a layout bug.
A third, subtler mistake comes from fixed pixel heights on table cells combined with variable font size in print: if the actual cell content does not fit into the fixed height, text can get clipped without the browser producing any error message. Fixed heights on table cells should generally be avoided in print stylesheets; the content should determine the row height, not the other way around.
7. Testing page breaks without an actual printer
The browser's print preview, usually reachable via Ctrl/Cmd + P, shows page breaks almost identically to the eventual PDF or paper printout, making it the fastest testing tool in everyday development. For automated regression tests, the same effect can be produced with headless browsers that export a page directly as a PDF, providing a reproducible basis for visual comparison tests.
It is especially important to test different amounts of data, not just a single sample order: a table with exactly one row, one with just enough rows that the break falls right before or right after the last row of a page, and a very long list spanning several pages. It is precisely these edge cases where most bugs in the break logic show up.
8. Limits of plain CSS versus server-side PDF export
Plain browser CSS reliably handles page breaks, repeated table headers, and simple margin boxes, but reaches its limits with more complex layout requirements such as precisely controlled running subtotals per page or pixel-perfect positioning for pre-printed forms. Such requirements can sometimes be worked around with CSS, but the result is often more fragile than a dedicated server-side PDF library.
For regulatorily sensitive documents like invoices with legally mandated formatting, a deliberate decision pays off: simple, frequently changed print views like order confirmations remain well maintainable with CSS, while highly formalized, less frequently changed documents like tax invoices can benefit from server-side PDF generation with exact layout control.
9. A checklist for multi-page tables in print
Before shipping an invoice or order confirmation layout, a short check pays off: is the header row correctly marked up in thead, is break-inside: avoid set on tr, have fixed pixel heights on cells been removed, and has the print preview been tested with both a very long and a very short sample list.
These four points cover the vast majority of problems that come up in practice with multi-page tables in print, and every one of them can be solved directly in CSS without JavaScript or extra server-side tooling.
| Rule | Effect | Repeats per page | Typical use |
|---|---|---|---|
tr { break-inside: avoid } |
Row stays intact or moves entirely to the next page | Re-evaluated per row | Order line items, invoice rows |
<thead> |
Column header is repeated automatically | Yes, on every following page | Table headers on long lists |
<tfoot> |
Footer appears only at the table's actual end by default | No, only once | Grand total at the end of the table |
@page { @top-right } |
Page-margin content like page number or logo | Yes, on every page | Document-wide headers and footers |
| Fixed cell height (px) | Can clip content, no error shown | Affects every row | Should be avoided in print |
Mironsoft
Modern CSS, layout architecture and rendering performance
CSS that stays maintainable instead of breaking with every change?
We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.
CSS Audit
Systematically uncovering specificity issues, cascade conflicts and unused selectors.
Architecture Refactoring
Introducing cascade layers, custom properties and design tokens cleanly.
Performance Tuning
Fixing layout thrashing, expensive selectors and rendering bottlenecks.
10. Summary
Print Tables and Page Breaks: The Essentials at a Glance
break-inside: avoid
Set on tr, it stops a row from being torn apart mid-break; the row instead moves to the next page as a whole.
thead as repeat header
A properly marked-up thead is automatically repeated on every following page by browsers, while tfoot only appears at the table's end.
Most common bug
Header rows that are technically a plain tr in tbody only show up on page one, leaving following pages without context.
Limits of CSS
Running per-page subtotals and pixel-perfect form positioning often require server-side PDF generation instead.