Tables

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.
  • colspan and rowspan attributes.
  • 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 a TFoot structure 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.