Components
Table
A data table with styled header, body, footer, and row hover states. Supports striped rows for easier scanning.
Table is a static primitive: it ships its own horizontally scrolling, rounded, bordered container, so you never have to wrap it yourself. Sorting, row selection wiring and pinned columns live one tier up, in the DataTable composite.
Basic usage
| Invoice | Customer | Amount | Status |
|---|---|---|---|
| INV-001 | Alice Johnson | $250.00 | Paid |
| INV-002 | Bob Smith | $150.00 | Pending |
| INV-003 | Carol White | $350.00 | Paid |
| INV-004 | David Brown | $450.00 | Overdue |
Striped variant
Use variant="striped" — or the boolean shorthand striped — for alternating row backgrounds.
| Invoice | Customer | Amount | Status |
|---|---|---|---|
| INV-001 | Alice Johnson | $250.00 | Paid |
| INV-002 | Bob Smith | $150.00 | Pending |
| INV-003 | Carol White | $350.00 | Paid |
| INV-004 | David Brown | $450.00 | Overdue |
With footer
| Invoice | Customer | Amount |
|---|---|---|
| INV-001 | Alice Johnson | $250.00 |
| INV-002 | Bob Smith | $150.00 |
| INV-003 | Carol White | $350.00 |
| INV-004 | David Brown | $450.00 |
| Total | $1,200.00 | |
Selected rows
Set data-state="selected" on a TableRow to mark it as chosen. The row picks up a muted tint and an accent rule along its bottom edge.
| Invoice | Customer | Amount |
|---|---|---|
| INV-001 | Alice Johnson | $250.00 |
| INV-002 | Bob Smith | $150.00 |
| INV-003 | Carol White | $350.00 |
| INV-004 | David Brown | $450.00 |
Two signals, on purpose. The bg-muted tint is deliberately faint — 1.07:1 against --card in light, 1.10:1 in dark — so text in the row stays comfortable to read. On its own it would fall short of the 3:1 that WCAG 1.4.11 asks of a meaningful non-text cue, and no tint is both faint and 3:1. The rule that does carry the information is border-b-app-accent-ink: 6.51:1 against the page or card fill, 6.09:1 at worst when the row sits on --muted. Note the -ink step — the bare accent (gold-300) is a background token and manages only 1.86:1 as a mark on white.
The rule sits on the bottom edge rather than the left because a <tr> scrolls sideways with the table: the left edge drifts out of the scroll box (and gets covered by DataTable's sticky pinned cells), while the bottom edge spans the full width and stays visible. Border width does not change with state, so nothing shifts.
Density
Row height is not hard-coded. TableHead takes its height from --table-head-h and TableCell its vertical padding from --row-py, so any table below a data-density="compact" subtree tightens from 40px/12px to 32px/6px without a single prop change. Wire the attribute with DensityProvider from @comitor/ui/shell; the scale is vertical only, so columns keep their padding and text never crowds the cell edge.
| Invoice | Customer | Amount |
|---|---|---|
| INV-001 | Alice Johnson | $250.00 |
| INV-002 | Bob Smith | $150.00 |
| INV-003 | Carol White | $350.00 |
| INV-004 | David Brown | $450.00 |
Caption
TableCaption renders a real <caption>, placed below the table, and names it for screen readers.
| Invoice | Customer | Amount |
|---|---|---|
| INV-001 | Alice Johnson | $250.00 |
| INV-002 | Bob Smith | $150.00 |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'striped' | 'default' | Visual style. Striped tints every even body row with the table-row-stripe token. |
striped | boolean | false | Convenience alias for variant="striped". Either one turns striping on. |
...props | React.ComponentProps<'table'> | — | All native table attributes are forwarded to the inner <table>; className lands there too, not on the scroll container. |
Sub-components
Every part is a thin wrapper over its native element and forwards all of that element's props.
| Prop | Type | Default | Description |
|---|---|---|---|
TableHeader | React.ComponentProps<'thead'> | — | The <thead>. Carries the muted header fill and the bottom border. |
TableBody | React.ComponentProps<'tbody'> | — | The <tbody>. Drops the border on the last row so it does not double up with the container edge. |
TableFooter | React.ComponentProps<'tfoot'> | — | The <tfoot>. Muted fill, top border, medium weight — for totals and summary rows. |
TableRow | React.ComponentProps<'tr'> | — | A <tr> with hover tint. Set data-state="selected" to mark it as chosen. |
TableHead | React.ComponentProps<'th'> | — | A header cell: uppercase, tracked, muted; its height follows the --table-head-h density token. |
TableCell | React.ComponentProps<'td'> | — | A body cell; its vertical padding follows the --row-py density token. |
TableCaption | React.ComponentProps<'caption'> | — | A caption rendered below the table, in muted text. |
Accessibility
- Uses semantic
<table>,<thead>,<tbody>,<tfoot>. - Column headers use
<th>for proper screen reader announcement. - The horizontal scroll container is built in —
Tablewraps itself in adata-slot="table-container"div withoverflow-auto, so wide tables scroll on small screens without an extra wrapper. - Give the table a name with
TableCaption, or anaria-labelon the<table>, whenever a page shows more than one. - Selection is never colour-only: a selected row is marked by an accent rule at 6.51:1 as well as its tint, and the tint alone would not clear the 3:1 non-text threshold. Pair it with a checkbox or a text summary of the selection all the same.