Tables
htmlbag supports the standard HTML table model: <table>, <thead>,
<tbody>, <tfoot>, <tr>, <td>, <th>, plus <colgroup> /
<col> for column-level styling.
Basic features
- Cell borders, padding, background.
colspanandrowspanattributes.- Cell width via
<col style="width: …">or per-cell CSS. - Headers (
<thead>) repeat at the top of each page when the table spans multiple pages. - Footers (
<tfoot>) of a table with a header repeat at the bottom of each page the table spans. A table with a<tfoot>but no<thead>prints its footer once, below the last body row. In tagged PDFs the footer is marked with aTFootstructure element so screen readers can distinguish summary rows from data rows. - CSS structural selectors apply to row and cell elements, so
alternating-row colors work via
tr:nth-child(even)without explicit classes — see the zebra-table example.
Cell alignment
Horizontal alignment in a cell follows text-align on <td> / <th>
or any ancestor element. Vertical alignment is controlled with
vertical-align: top | middle | bottom on the cell or — for a whole
column — on the <col> element. The value propagates down through
the inheritable-style cascade, so setting vertical-align: top on the
<table> itself affects every cell that doesn’t override it.
Position
margin-left moves a table to the right, with its borders and
background, and a table that breaks across pages keeps the shift on
every page, its repeated header included:
table.indented { margin-left: 30pt; }margin: 0 auto centers a table, and margin-left: auto moves it to
the right. A table with a width is placed by that width, one without a
width by the width of its content.
A table inside a block container sits in the container’s content box,
as a paragraph next to it does: the container’s padding moves it, and
position: relative on the table moves it from there, also when it
breaks across pages.
Multi-page tables
A table breaks between its rows. When it does not fit in the space left on the page, it is placed row by row: it starts right below the content before it, a header repeats on every page, and the content after the table goes on below its last row. When a later page has a different content width, the rest of the table is set again at that width.
Rows that a rowspan joins stay on one page: the page break is taken before the first of them, and a group taller than a page starts at the top of one and breaks like other rows.
Rows that break across pages
A row is set as one piece and moves to the next page whole, as with
break-inside: avoid. With break-inside: auto (or
page-break-inside: auto) on the <tr>, a row that does not fit is
split where the page ends instead, and the rest goes on at the top of
the next page, below the repeated header. This is for rows taller than
a page, such as long clauses in a contract:
tr.clause { break-inside: auto; }The cells break between their lines and paragraphs, and between the
rows of a nested table. Both parts keep the cells’ borders, padding
and background, and the line above the row is drawn again on the next
page. Some rows stay whole even with auto: a row that a rowspan
reaches into or out of, since the spanning cell is drawn with its
first row. The header rows of a nested table are not repeated after a
break, and when the next page has a different content width, the rest
of a split row keeps the width it started with.
Footnotes inside table cells
Footnotes inside table cells are extracted at table-build time and attached to the table’s VList. They land on the page where the table’s last row sits.
Floats inside table cells
Floats inside table cells (top or bottom) are extracted alongside footnotes and attached to the table VList. They behave like any other float — landing on the page where the table sits, in the appropriate top/bottom stack.