Tables

Tables

htmlbag supports the standard HTML table model: <table>, <caption>, <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.

Captions

A <caption> is set as a block above the table, centered by the default stylesheet. caption-side is not read, so a caption is always above its table. It keeps with the table at a page break unless it declares a break-after of its own. In tagged PDFs it is a Caption structure element, the first child of the table’s Table.

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.

Rows of a fixed height

-bag-fixed-height on a <tr> makes the row exactly that high, and the row never breaks inside. Content that does not fit draws past the row, while its background and borders keep the height. It wins over height on the same row, which is a minimum height, and a cell’s height in a fixed row is ignored. On a cell the property has no effect.

tr.label { -bag-fixed-height: 16pt; }

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, also when the table breaks across pages.

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, in the appropriate top/bottom stack, and land with the footnotes on the page where the table’s last row sits.