Files
skills/docs/ui/react/react-ui/src/components/data-table/data-table.md
2026-05-29 08:20:56 +01:00

8.4 KiB

DataTable

(no submodules)

- data-table.variants — `dataTableVariants`, `dataTab…
  • data-table.variants — dataTableVariants, dataTablePaginationVariants, and dataTableSortHeaderVariants drive the chassis <DataTable> o…

  • data-table.variants — dataTableVariants, `dataTab…

  • data-table.variants — dataTableVariants, dataTablePaginationVariants, and dataTableSortHeaderVariants drive the chassis <DataTable> o…

  • data-table.variants — dataTableVariants, `dataTab…

  • data-table.variants — dataTableVariants, dataTablePaginationVariants, and dataTableSortHeaderVariants drive the chassis <DataTable> o…

  • data-table.variants — dataTableVariants, `dataTab…

  • data-table.variants — dataTableVariants, dataTablePaginationVariants, and dataTableSortHeaderVariants drive the chassis <DataTable> o…

  • data-table.variants — dataTableVariants, dataTablePaginationVariants, and dataTableSortHeaderVariants drive the chassis <DataTable> o…

Purpose

<DataTable> is the chassis headless data-table built on @tanstack/react-table (v8) and the chassis <Table> family. It is a suaveplan-only addition — shadcn does not ship data-table as a registry item upstream (per Wave 3a's reconciliation finding #7); shadcn's docs treat data-tables as recipes consumers build on top of the <Table> primitive. The chassis exposes <DataTable> as a discrete component so consumers do not hand-roll sorting / pagination / selection / empty-state treatment per surface.

The component is generic over TData (the row shape) and TValue (the column value union, defaults to unknown). Consumers build column definitions with the re-exported createColumnHelper from @tanstack/react-table. Every chassis-rendered string (no-results, pagination, rows-per-page, selection summary) flows through the active i18n via t() calls keyed on bundled react-ui.dataTable.* translation keys. The selected key uses ICU plural so the count agrees with the active locale's plural rules — Welsh's six-form scale is bundled.

Features

  • Built on TanStack Table v8 — the de-facto headless table for React.
  • Sortable columns — click any sortable column header to toggle asc / desc / none; the header button shows a chevron icon indicating the active sort direction.
  • Pagination — pageSize defaults to 10 rows; previous / next buttons disable at the boundaries.
  • Row selection (opt-in via enableRowSelection) — chassis renders a selection summary using ICU plural translation.
  • Empty state — when no rows match, the chassis renders the bundled react-ui.dataTable.noResults message in a single full-width row.
  • Stable row keys via getRowId (recommended) — without it, TanStack uses array-index keys which collide on re-order.
  • data-state="selected" on selected rows so chassis <TableRow> hover / selected styling activates automatically.
  • Re-exports createColumnHelper so consumers don't need to add @tanstack/react-table as a direct dep.

Usage: Basic Example

import { createColumnHelper, DataTable } from "@suaveplan/react-ui";

interface Invoice {
    readonly id: string;
    readonly invoice: string;
    readonly status: string;
    readonly amount: number;
}

const columnHelper = createColumnHelper<Invoice>();

const COLUMNS = [
    columnHelper.accessor("invoice", { header: "Invoice" }),
    columnHelper.accessor("status", { header: "Status" }),
    columnHelper.accessor("amount", {
        header: "Amount",
        cell: (info) => `$${info.getValue().toFixed(2)}`,
    }),
];

export function InvoiceDataTable({ data }: { data: Invoice[] }) {
    return (
        <DataTable<Invoice>
            columns={COLUMNS}
            data={data}
            getRowId={(row) => row.id}
        />
    );
}

Usage: Advanced Example

import { createColumnHelper, DataTable } from "@suaveplan/react-ui";

interface User {
    readonly id: string;
    readonly name: string;
    readonly email: string;
    readonly role: "admin" | "member";
}

const columnHelper = createColumnHelper<User>();

const COLUMNS = [
    columnHelper.display({
        id: "select",
        header: "",
        cell: ({ row }) => (
            <input
                type="checkbox"
                checked={row.getIsSelected()}
                onChange={row.getToggleSelectedHandler()}
                aria-label="select-row"
            />
        ),
    }),
    columnHelper.accessor("name", { header: "Name", enableSorting: true }),
    columnHelper.accessor("email", { header: "Email", enableSorting: false }),
    columnHelper.accessor("role", { header: "Role", enableSorting: true }),
];

export function UserDataTable({ users }: { users: User[] }) {
    return (
        <DataTable<User>
            columns={COLUMNS}
            data={users}
            getRowId={(row) => row.id}
            enableRowSelection
            pageSize={5}
            initialSorting={[{ id: "name", desc: false }]}
        />
    );
}

API Reference

<DataTable<TData, TValue = unknown>>

Prop Type Default Description
columns ReadonlyArray<ColumnDef<TData, TValue>> — Column definitions; build with createColumnHelper<TData>().
data ReadonlyArray<TData> — The rows to render. Must be stable identity-wise (use useMemo on the consumer side).
getRowId (row: TData, index: number) => string undefined Stable row id for selection / keying. Recommended.
enableRowSelection boolean false When true, renders a selection summary using ICU plural translation.
pageSize number 10 Rows per page.
initialSorting SortingState [] Initial sort state.
noResultsKey TranslationKey react-ui.dataTable.noResults Empty-state message.
rowsPerPageKey TranslationKey react-ui.dataTable.rowsPerPage Pagination footer label.
selectedKey TranslationKey react-ui.dataTable.selected ICU-plural selection summary; receives { count }.
previousKey TranslationKey react-ui.dataTable.previous Previous-page button label.
nextKey TranslationKey react-ui.dataTable.next Next-page button label.

createColumnHelper<TData>()

Re-exported from @tanstack/react-table. Returns a builder with accessor, display, and group methods for typed column definitions.

Errors

The DataTable component calls useTranslation() to resolve the chassis-rendered translation keys. It MUST render inside a <TranslationProvider> / <ReactUiProvider>; otherwise useTranslation throws.

Implementation Notes

  • The component is generic over TData and TValue so consumers get full type-safety on column definitions and cell renderers. The cast to ColumnDef<TData, TValue>[] inside useReactTable is a TanStack ergonomics workaround — TanStack's data accepts TData[] but its columns slot accepts a value-erased shape, so the chassis re-asserts the typed shape at the boundary.
  • The selected plural translation includes the Welsh six-form scale (zero | one | two | few | many | other) so the count agrees with the active locale's plural rules. Other locales (en-GB, zh-CN, ar-AE, ru-RU) use their respective plural categories from the bundled JSON.
  • getRowId is strongly recommended even when data items contain an id field — without a stable getter, TanStack uses array index keys, which collide when consumers reorder the data array. The chassis does NOT default getRowId to (row) => row.id because not every TData shape has an id field.
  • The pagination controls are intentionally bare <button> elements with border px-2 py-1 — this keeps the data-table self-contained without forcing a <Button> import. Consumers wanting fancier pagination compose their own footer instead of using the built-in one (read pagination state from table.getState().pagination and call table.setPageIndex / table.setPageSize directly).
  • The sort indicator chevron icon is aria-hidden="true" because the column header text already conveys the sort target; screen-readers should not double-announce.
  • Empty state spans colSpan={columns.length} so the no-results message centres across the full table width regardless of column count.