Eine Tabelle, die auf eine Seite passt, ist einfach. Schwierig wird es am Seitenumbruch: Der Tabellenkopf kommt nicht wieder, eine Zeile wird mittendurch geschnitten, die Gesamtsumme steht unter den ersten dreißig Zeilen, eine Fußzeile liegt über der letzten Zeile. Die meisten Ratschläge dazu stammen aus einer Zeit, in der Browser anders paginiert haben, und werden seitdem weiterkopiert.
Also haben wir nachgemessen. Wir haben 21 kleine Tabellen-Dokumente durch unseren Render-Worker geschickt — dasselbe Headless Chromium in Version 153.0.8010.12, das jedes PDF von Formfeed druckt — und die fertigen PDFs mit pdf.js wieder ausgelesen, um zu sehen, auf welcher Seite jede Zeile, jeder Tabellenkopf und jede Summe gelandet ist. Jeder Fall unten verlinkt sein PDF.
Der Ausgangspunkt
Jedes Formfeed-Dokument beginnt mit einem kurzen Print-Reset. Vier seiner Zeilen betreffen Tabellen
(sinngemäß: Die Regel für table steht in Wirklichkeit in :where(), und das CSS der Vorlage kommt
nach dem Reset, sodass jede Regel der Vorlage gewinnt):
table {
break-inside: auto;
border-collapse: collapse;
}
tr {
break-inside: avoid;
}
thead {
display: table-header-group;
}
tfoot {
display: table-footer-group;
}
Die Zeile für tr stammt von uns, nicht von Chromium: Ohne sie teilt Chromium eine Zeile dort, wo
die Seite endet. Die Fälle verwenden A4 mit 20 mm Rand, Schrift in zehn Punkt und eine Fußzeile mit
Seitenzahl aus Chromiums Footer-Template, sofern nicht anders angegeben. Die Zeilen sind nummeriert,
bei hohen Zeilen auch deren Textzeilen (08·5 ist die fünfte Textzeile von Zeile 8), damit man eine
Teilung sieht.
Was Chromium einhält
| Fall | CSS | Was das PDF zeigt | |
|---|---|---|---|
| 60 Zeilen | — | der Tabellenkopf steht oben auf Seite 2 wieder | |
| Zeilen mit je sechs Textzeilen | tr { break-inside: avoid } |
7 ganze Zeilen pro Seite, keine geteilt | |
| dieselben Zeilen | tr { break-inside: auto } |
Zeilen 8, 16 und 24 über zwei Seiten geteilt | |
| eine Zeile mit 90 Textzeilen | tr { break-inside: avoid } |
die Zeile beginnt eine neue Seite und wird dann doch geteilt; alle 90 Textzeilen stehen im PDF | |
Gruppen aus sechs Zeilen, jede ein eigenes tbody |
tbody { break-inside: avoid } |
Seite 1 endet nach Zeile 30, einer Gruppengrenze, statt nach Zeile 32 | |
| eine Überschrift unten auf der Seite | h2 { break-after: avoid } |
die Überschrift wandert mit ihrer Tabelle auf die nächste Seite | |
| die letzte Zeile allein auf einer Seite | tbody tr:last-child { break-before: avoid } |
Zeile 8 geht mit: 7 Zeilen, dann 2 | |
die letzten zwei Zeilen als eigenes tbody |
tbody.tail { break-inside: avoid } |
ebenso 7 und 2 | |
die Tabelle in einem Flex-, Grid-, overflow: auto-, overflow: hidden- oder absolut positionierten Container |
— | paginiert wie eine nackte Tabelle, Kopf wiederholt | flex · grid · auto · hidden · absolute |
Die letzte Zeile hat uns am meisten überrascht, denn jeder dieser Container taucht in Ratschlägen auf, warum eine Tabelle nicht mehr paginiert. In Chromium 153 hält keiner von ihnen sie auf.
Die beiden Zusammenhalte-Regeln gehören in jede Vorlage. Ohne sie bleibt die Überschrift allein zurück:

und die letzte Zeile einer Tabelle steht allein auf einer eigenen Seite:

Mit break-before: avoid an der letzten Zeile nimmt Chromium die Zeile davor mit, sodass keine Seite
nur eine einzige Zeile trägt:

Und so sieht eine hohe Zeile aus, wenn nichts Chromium bittet, sie zusammenzuhalten — der Betrag bleibt auf Seite 1, die letzten beiden Textzeilen der Beschreibung gehen auf Seite 2:

Drei Arten, es falsch zu machen
Die Summe auf jeder Seite
tfoot wirkt wie der Platz für eine Summe. Aber ein tfoot ist eine Fußgruppe, und Chromium
wiederholt sie unten auf jeder Seite, die die Tabelle erreicht, genau wie thead oben. Die
Gesamtsumme erscheint auf Seite 1 unter Zeile 31:

Zwei Auswege. Die Summe kommt in ein eigenes, letztes tbody. Oder das tfoot bleibt und wird zu
einer gewöhnlichen Zeilengruppe, tfoot { display: table-row-group; } — dann steht es einmal, auf der
letzten Seite, sofern es im Markup hinter dem tbody steht.
HTML erlaubt das tfoot auch vor dem tbody; mit der Umstellung steht so eine Summe
über der ersten Zeile.
Einen Übertrag kann die Wiederholung auch nicht tragen. Chromium wiederholt eine Kopie desselben
Inhalts; eine Zeile, die pro Seite die Beträge darüber aufsummiert, kann ein tfoot nicht leisten.
Eine halbierte Zeile
Steckt die Tabelle in einem Element mit display: inline-block — ein verbreiteter Weg, sie zu
zentrieren oder auf ihren Inhalt schrumpfen zu lassen —, paginiert Chromium die Tabelle darin nicht
mehr. Es druckt den Kasten so, wie er ist, über die Seitenkante: Zeile 33 wird mittendurch
geschnitten, und Seite 2 hat keinen Tabellenkopf.

Ein Block als Hülle tut das nicht. thead { display: block; }, das manches CSS für scrollbare
Tabellen setzt, lässt die Zeilen ganz, verliert aber den wiederholten Kopf.
Die Zeile unter der Fußzeile
Eine Fußzeile mit position: fixed; bottom: 0 wiederholt sich zwar auf jeder Seite. Die Tabelle weiß
aber nichts von ihr. Zeile 32 rutscht darunter, und weil die Tabelle meint, Zeile 32 habe gepasst,
geht es auf Seite 2 mit Zeile 33 weiter — Zeile 32 ist nirgends ganz zu sehen:

Der saubere Weg ist, die Fußzeile gar nicht in die Seite zu setzen. Chromiums eigenes
Footer-Template wird im unteren Rand gedruckt, wo kein Inhalt hinkommt; alle anderen Fälle in diesem
Beitrag haben eines, und keiner hat dadurch eine Zeile verloren. Muss die Fußzeile doch in die Seite,
löst die Wiederholung, die das erste Problem verursacht hat, dieses hier: Eine leere tfoot-Zeile so
hoch wie die Fußzeile wiederholt sich auf jeder Seite und hält die Zeilen von ihr fern.
<tfoot>
<tr class="reserve">
<td colspan="3"></td>
</tr>
</tfoot>
tr.reserve td {
height: 10mm;
padding: 0;
border: 0;
}

Die Kurzfassung
- Zeilen zusammenhalten mit
tr { break-inside: avoid }, Gruppen von Zeilen mit je einemtbodyundbreak-inside: avoid. - Überschriften bei ihrer Tabelle halten:
h2 { break-after: avoid }. - Die letzte Zeile nicht allein lassen:
tbody tr:last-child { break-before: avoid }. - Summen in ein letztes
tbody, oder dastfootalstable-row-grouphinter dastbody. - Kein
inline-blockum eine Tabelle, keindisplay: blockan ihremthead. - Fußzeilen gehören ins Footer-Template, nicht in
position: fixed.
In Formfeed
Der Print-Reset oben ist standardmäßig aktiv, und jede seiner Zeilen lässt sich überschreiben. Der Tab „Fußzeile“ im Editor ist Chromiums Footer-Template; eine Fußzeile von dort steht also im Rand. Die paginierte Vorschau im Editor ist darauf gebaut, wie das PDF umzubrechen, und Echtes Rendering schickt die Vorlage durch denselben Worker, durch den diese Fälle gegangen sind. Die Dokumentation zeigt den ganzen Reset unter Vorlagen schreiben und die Regeln von oben in ihrem Abschnitt zu Seitenumbrüchen, und das Berichtsbeispiel ist ein zweiseitiges Dokument mit Diagramm und einer Tabelle mit Monatszahlen.
Gerendert und gemessen am 21. September 2026 mit Chromium 153.0.8010.12. Chromiums Layout ändert sich zwischen Versionen; wir wiederholen die Fälle, wenn sich das Chromium des Workers ändert.