Skip to main content

Table

A styled <table> inside a horizontally scrolling wrapper. It is purely presentational: sorting, selection and pagination state stay with you (or with @zuilib/table, the TanStack data grid built on these parts). Every rule is drawn by the cells (border-separate, zero spacing), so borders travel with a sticky header.

import Table from '@zuilib/components/table'

Variants

line rules every row. striped paints alternate body rows on --muted. bordered outlines every cell as a grid.

Loading example

Sizes

sm for dense data, md for reading. Cell padding comes from --table-cell-padding-x/y-sm and -md (falling back to spacing multiples) and the text size from --text-xs / --text-sm; the caption inherits it.

Loading example

Sortable heads and selectable rows

sortable wraps the head's children in a button with a sort indicator and sets aria-sort from sortDirection. interactive rows get a pointer and hover feedback; with an onClick they also become a tab stop where Enter and Space fire the click. selected paints the row --accent and sets aria-selected, which assistive tech announces inside a role="grid" table.

Loading example

stickyHeader pins the header cells to the top of the scroll container. The wrapper is that container, so give it a height through containerClassName; otherwise the page scrolls and the header goes with it. One header row only.

Loading example

Props

TableProps extends the <table> attributes with:

PropTypeDefaultDescription
variant'line' | 'striped' | 'bordered''line'
size'sm' | 'md''md'Cell padding from --table-cell-padding-x/y-*; text --text-xs / --text-sm
stickyHeaderbooleanfalseHeader cells stick to the top of the container; size it with containerClassName
fullWidthbooleantruew-full on the table
classNamestringMerged last onto the <table> (the ref target)
containerClassNamestringMerged last onto the scrolling wrapper

Table.Row

TableRowProps extends the <tr> attributes with:

PropTypeDefaultDescription
selectedbooleanfalse--accent paint, data-selected and aria-selected
interactivebooleanfalseBody rows only: pointer cursor and hover. With onClick also tabIndex=0, a focus ring and Enter / Space activation

Table.Head

TableHeadProps extends the <th> attributes (minus the deprecated align) with:

PropTypeDefaultDescription
align'start' | 'center' | 'end''start'Text alignment; also steers the sort button content
sortablebooleanfalseWraps children in a sort button and sets aria-sort
sortDirection'asc' | 'desc' | 'none''none'
onSort() => voidCalled when the sort button is activated
sortIndicatorReactNodeReplaces the default arrows; wrapped in data-slot="table-sort-indicator" with data-sort
sortButtonClassNamestringMerged last onto the sort button

Table.Cell and Table.Caption

PropTypeDefaultDescription
align'start' | 'center' | 'end''start'On Table.Cell; end for numbers
side'top' | 'bottom''bottom'On Table.Caption: where the caption renders

Table.Header, Table.Body and Table.Footer take the plain <thead>, <tbody> and <tfoot> attributes. All parts are also named exports (TableRow, TableHead, …).

Slots

SlotElementNotes
[data-slot="table-container"]divThe scrolling wrapper; data-variant, data-size
[data-slot="table"]tabledata-variant, data-size, data-sticky-header
[data-slot="table-header"]thead
[data-slot="table-body"]tbody
[data-slot="table-footer"]tfootOn --muted, ruled off from the body
[data-slot="table-row"]trdata-selected, data-interactive (present or absent)
[data-slot="table-head"]thscope="col"; data-align, data-sortable, data-sort
[data-slot="table-sort-button"]buttonFills a sortable head; data-sort
[data-slot="table-sort-icon"]svgDefault indicator; data-sort
[data-slot="table-sort-indicator"]spanWraps a custom sortIndicator; data-sort
[data-slot="table-cell"]tddata-align
[data-slot="table-caption"]captiondata-side
/* Consumer CSS: denser tables everywhere */
:root {
--table-cell-padding-x-md: calc(var(--spacing) * 2);
--table-cell-padding-y-md: calc(var(--spacing) * 1.5);
}

Accessibility

  • A real <table>; Table.Head renders <th scope="col">. Name the table with Table.Caption or aria-label.
  • A sortable head sets aria-sort="ascending" | "descending" | "none" and puts its children in a <button type="button">, so sorting is keyboard operable with an inset --ring focus ring.
  • An interactive row with onClick gets tabIndex=0, a focus-visible outline and activates on Enter and Space (Space does not scroll). Leave onClick off the row when the action lives on a control inside it, so the row is not a second tab stop.
  • selected sets aria-selected, which screen readers announce only inside role="grid"; pass that role to <Table> for whole-row selection, or convey the state with a checkbox in the row.
  • Sticky header cells stay opaque (bg-background) so body rows slide underneath them.
  • Checkbox: row selection checkboxes ([role=checkbox] cells drop their right padding).
  • Badge: status cells.
  • Theming: retheme density through the --table-cell-padding-* tokens.