Components
Data Table
Sortable, responsive table on TanStack Table — the foundation under every other table in the system.
The headless TanStack engine rendered through the design system's semantic table primitives. Click a sortable column header — sorting, loading skeletons, and the failed-state view are handled for you, not something each screen re-implements.
| First Passage to the West | 2 | GoldLeaf |
| Journey through the Clouds | 2 | SilverLeaf |
| Rainforest to Gold Rush | 2 | GoldLeaf |
- RouteFirst Passage to the WestDays2ServiceGoldLeaf
- RouteJourney through the CloudsDays2ServiceSilverLeaf
- RouteRainforest to Gold RushDays2ServiceGoldLeaf
Props
Prop
Type
Implementation notes
- Responsive by layering, not by hiding the table. Below the
mdbreakpoint the same sorted row model renders as a stacked card list instead of a horizontally-scrolled table — set a column'smeta.responsiveClassName(e.g."hidden lg:table-cell") to drop a low-priority column abovemd, rather than trying to cram every column into a phone width. errorreplaces the table, it doesn't sit beside it. Pass a string toerror(withonRetry) and the component swaps in an <ErrorState>. UseerrorTitleandretryLabelwhen the default copy is too generic; don't build a separate error branch around<DataTable>in the consumer.- Sortable headers announce their state. A sortable column's
<th>carriesaria-sort("ascending"/"descending"/"none") automatically from TanStack's sort state — this is why sorting should stay wired through the column def'senableSorting/TanStack sort state rather than a hand-rolled click handler that bypasses it. - Column behavior can be controlled. Pair
columnOrderwithonColumnOrderChange, andcolumnSizingwithonColumnSizingChange. SetenableColumnReorderingandenableColumnResizingto expose the controls.stickyHeaderkeeps headings visible inside a vertically scrolling table region bounded bymaxHeight. - Empty content is built in. Set
emptyTitle, with optionalemptyDescriptionandemptyAction, instead of branching around the table. Loading, empty and error then share one component contract.
Accessibility
- Give every table a real
caption— it renders as a visually-hidden<caption>, not decoration. - Move buttons and resize separators have table-specific accessible labels. Resize separators accept Left/Right Arrow in 16-pixel steps, while reorder buttons announce the resulting position. Pointer resizing remains available. These controls are separate from the sort button, so resizing or moving a column does not change its sort direction.
- Set
meta.controlLabelwhen two columns intentionally share a visible heading. The unique label is used by resize and reorder controls while the visible text stays faithful to the product language. getRowIdis optional but strongly recommended whenever rows can reorder (sorting, refetch) — without it, React keys by array index, which can silently misattribute state (e.g. row selection) to the wrong row after a re-sort.
Do / Don't
- Do reach for
SelectableDataTableinstead of hand-adding a checkbox column here — it's the same table with row selection layered on, not a fork. - Do pass
isLoadingrather than conditionally rendering the table only once data arrives — the skeleton keeps the column layout stable instead of the page reflowing when data lands. - Do keep sizing in controlled state for the current view when users may resize columns. Browser cache restore applies to column order and sort only in RMS-1557; persisted widths and cross-device preferences are separate product work.
- Don't implement pagination inside a
<DataTable>consumer's column defs or row model — pagination is a concern of whatdatayou pass in, not of this component. A numbered pager suits an offset/page-indexed API; an opaque-cursor API needs a different pager shape entirely (prev/next only, no page count) — don't force one pattern onto the other.