Table Primitives
Last Updated: September 2026TablePrimitives is the layout primitive underneath Table: the grid, the variants, the row heights and the sticky header, with the markup left to you. Reach for it when a column definition can't express the layout.
Anatomy
link- 1Header
- 2Body
- 3Columns
- 4Pagination
Variants
linkContained
Bordered, rounded surface with a pagination footer. Renders exactly the rows you pass, so slice the page yourself.
Full page
No border, fills its parent, scrolls internally with a sticky header. The parent needs a definite height.
Skeleton rows
TablePrimitives.SkeletonRow fills a row with placeholders at the same height, so nothing shifts when the data lands.
Group row
TablePrimitives.GroupRow labels the rows under it, in one cell across every column — at a data row's height, unlike an expanded panel.
Expanded row
TablePrimitives.ExpandedRow spans every column, for a panel of your own under the row it belongs to.
Sizes
linkRow height is uniform per size — that is what lets a virtualized Table position rows without measuring them.
Usage Guidelines
linkWhen to use
- •You already have the rows in the shape you want to render and need markup, not behaviour.
- •The layout is unusual enough that a column definition can't express it — spanning cells, nested grids, a bespoke header.
- •You are building another component on top and want the styling, variants, and sticky header for free.
When not to use
- •For an ordinary list of records — use Table, which owns pagination, virtualization, selection, and pinning.
- •For long lists: TablePrimitives renders every row you hand it. Table virtualizes.
- •For layout that isn't tabular data — use Stack or CSS grid directly.
Declare the tracks once on the root. Header and body share them, so a column lines up across every row.
Don't size cells individually. A cell is a subgrid item; a width on it fights the track and rows stop aligning.
Pair truncate with a track the column can shrink into, so one long value clips instead of widening the table.
Don't put a fullPage table in a parent with no definite height — it grows to fit every row and the page scrolls instead.
Behaviors
linkLayout
- •The root is a CSS grid; rows are subgrids of it. One columns value governs every row.
- •Content-based tracks (auto, max-content) size to the widest cell in the whole table.
- •Fixed tracks wider than the container overflow into a horizontal scroll rather than squashing.
Variants
- •contained owns a footer, so it requires pagination — it does not slice data, it renders the rows it is given.
- •fullPage scrolls internally, keeps the header sticky, and takes no pagination props.
- •A fullPage table sizes itself from its parent: give it a flex-1 min-h-0 track inside a column of viewport height.
Rows
- •Row height is fixed per size (36px compact, 56px comfortable) so lists don't jitter as content changes.
- •TablePrimitives.SkeletonRow, TablePrimitives.GroupRow and TablePrimitives.ExpandedRow are rows too — put them inside TablePrimitives.Body, not around it.
- •TablePrimitives.ExpandedRow spans all columns via grid-column: 1/-1, so its height is its content's.
Content Guidelines
linkHeaders
- •Short noun phrases in sentence case. Don't repeat the table's own title in every header.
- •Label the unit in the header (Salary (USD)) rather than in every cell.
Cells
- •Right-align nothing by default; align numbers only when a column is scanned for magnitude.
- •Format in the cell, not the data — toLocaleString for numbers and dates.
- •Use synthetic clinic and person names in examples — no real patient data.
Accessibility
linkText & Labels
- •Give the table an accessible name with aria-label or aria-labelledby when the surrounding heading isn't adjacent.
- •Set aria-busy on TablePrimitives.Body while placeholders stand in for real rows.
ARIA attributes
- •The grid layout means the native table roles are restated explicitly — the root is a table, rows are rows, cells are cells.
- •Screen readers announce a row's position from those roles, so don't render rows outside TablePrimitives.Body.
Keyboard Support
- •TablePrimitives itself adds no key handling; controls inside cells keep their own tab order.
- •Rows become focusable only when Table is given onRowClick or getRowHref.
WCAG Compliance Standards
FAQs
linkTablePrimitives or Table?
Table unless you need bespoke markup. TablePrimitives is the styling and layout primitive Table is built on; it has no pagination, virtualization, selection, or pinning of its own.
Why is there one columns prop instead of widths on the cells?
Rows are CSS subgrids of the root's tracks. That is what keeps columns aligned across a virtualized list where only some rows exist.
Does contained paginate my data?
No. It renders the rows you pass and draws the footer from pagination. Slice the page yourself, or use Table.