How do I build a table?
TanStack Table, via ui-kit's DataTable — the one true table.
Prescribed: TanStack Table via DataTable. Don't
reach for another table library or hand-roll sort/pagination state — DataTable already wraps
TanStack's headless engine with this design system's responsive layout (a real <table> on
desktop, stacked cards on phone), sortable headers with correct aria-sort, and built-in
loading/error states.
The approved implementation
Define your rows' shape and a plain TanStack ColumnDef[], then hand both to DataTable with an
accessible caption. Sorting, responsive column hiding (via each column's meta.responsiveClassName),
and the loading/error states all come for free — you don't wire any of it yourself.
| 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
When to reach for something else
- Rows can be selected (bulk actions, checkboxes) →
SelectableDataTablebuilds on the sameDataTableengine with a selection column already wired. - Just showing a handful of static key/value rows, no sorting or responsiveness needed → the
plain
Tableprimitives are lighter-weight and enough. - Metric/KPI rows with a trend column →
MetricTablecomposesDataTablewith aDeltaTextchange column already built in.
Read-only tables vs. permission-gated actions
These are two different things, and mixing them up reads as a bug to the user.
-
The surface has no write actions at all. Nothing is offered because nothing belongs here — the screen is a search-and-view surface and editing lives elsewhere. Render no action column and no save bar. Don't render disabled buttons as a hint that editing exists somewhere; the user can't tell a permanently-absent capability from one they're merely not allowed to use. Example: the Cost and Selling tabs in
Pages/Price Entries. -
The action exists, but this user may not perform it. Render it shown but disabled, with a
restrictedReasonexplaining why — seeRestrictedIconButton. Removing it hides the shape of the product and makes permissions look like missing features. Example: Edit/Delete on the Price Seasons table, which only the Product Team may use.
The test to apply: would a differently-permissioned user see this control enabled? Yes → show it disabled. No, nobody would → leave it out.
Do / Don't
- Do pass a real, accessible
caption— it's rendered as a visually-hidden<caption>, not decoration. - Do use
isLoading/error/onRetryfor anything backed by a real fetch — see the Data-fetching recipe for the server side of this. - Don't implement your own sort state, pagination, or a second table component for "just this
one case" — extend
DataTable'scolumnsinstead; that's exactly what itsmetaescape hatch is for.