Capítulo 18 de 54
Headers are the <thead> equivalent of cells, sourced from header groups (headerGroup.headers), and the one non-obvious piece is rowSpan/isPlaceholder handling for column trees where leaf columns sit at different depths.
headerGroup.headers for a given row, or use table APIs like table.getFlatHeaders() for a flat list across all header rows; column-visibility/pinning add more variants (table.getStartLeafHeaders(), table.getEndFlatHeaders(), etc.).header.id: for simple (non-grouped) tables, equals the parent column.id; for group columns or placeholder cells, it's a composite of header family/depth/column id/header-group id.colSpan (columns spanned horizontally), rowSpan (rows spanned vertically for uneven column trees — see below), depth (which header row this belongs to), isPlaceholder (true for filler cells above a shallower leaf column's real header), subHeaders (child headers, empty for leaves).header.index (position left-to-right within its header group) is not the same as header.depth (which header row/group it's in) — an easy naming trap.column and parent header group.flexRender(header.column.columnDef.header, header.getContext()) — the header column option can be a string, JSX, or a function, and flexRender normalizes all three.| Header property | Meaning |
|---|---|
colSpan | how many leaf columns this header covers, horizontally |
rowSpan | how many header rows this header covers, vertically (see Header Row Spanning below) |
depth | which header row (0-indexed) this header sits in |
isPlaceholder | true for filler cells above a shallow leaf's real header |
subHeaders | child headers (empty array for leaf headers) |
Header Row Spanning — when the column tree is uneven (some leaf columns nested deeper than others), each shallow leaf produces a chain of placeholder headers above its real header. The top placeholder in that chain reports the chain's full rowSpan; every header it covers (including the real leaf header below it) reports rowSpan: 0. To merge those visually into one tall <th>, skip any header with rowSpan === 0 and render the rest with the rowSpan attribute — this check replaces the usual isPlaceholder-based empty-cell rendering, for <thead> only:
{headerGroup.headers.map((header) =>
header.rowSpan === 0 ? null : (
<th key={header.id} colSpan={header.colSpan} rowSpan={header.rowSpan}>
{flexRender(header.column.columnDef.header, header.getContext())}
</th>
)
)}
This convention is <thead>-only: footer groups render header rows in reverse order, which puts a spanning placeholder below the cells it would cover, so <tfoot> still uses the plain isPlaceholder → empty-cell pattern. The body-cell equivalent of row spanning is the separate cellSpanningFeature (Cell Spanning Guide).
flexRender, never by reading header.column.columnDef.header directly as if it were always a string.rowSpan === 0 skip pattern in <thead> — the ordinary isPlaceholder empty-cell pattern is for <tfoot> only (or flat, even trees).header.getSize()-style APIs.headerGroup.headers comes from.