DataTable
Signal-driven data table with client/server sort, filter, pagination, multi-selection, expandable rows, and CSS content-visibility row containment for large datasets
Category: display · WCAG 2.2-AA · table, data, grid, sort, filter, pagination, selection
Sizes
- compact
- normal
- relaxed
States
- default
- loading
- empty
Props
| Prop | Type | Default | Description |
|---|---|---|---|
virtualized | boolean | false | Render only the visible row window for large datasets. |
rowHeight | number | 40 | Fixed row height in px, used to compute the virtualized window. |
windowSize | number | 20 | Number of rows rendered in the virtualized window. |
overscan | number | 3 | Extra rows rendered above/below the window to smooth scrolling. |
columns | Column<Row>[] | — | The column definitions describing each table column. |
rows | Row[] | — | The row objects to render — one table row per array element. |
getRowId | (row: Row) => string | — | Returns a stable unique id for a row. |
sort | SortState | — | The controlled sort state. |
defaultSort | SortState | — | The initial sort state when uncontrolled. |
sortMode | 'client' | 'server' | 'client' | Whether sorting is handled client-side or by the server ('client' | 'server'). |
onSortChange | (sort: SortState | undefined) => void | — | Called with the new sort state when it changes. |
searchable | boolean | false | When true, shows a search/filter input. |
pagination | { pageSize: number; pageSizeOptions?: number[] } | — | Pagination configuration (page size and options). |
selection | { mode: 'single' | 'multi'; selected?: string[]; onChange?: (ids: string[]) => void } | — | Row-selection configuration (mode and selected ids). |
batchActions | { label: string; onClick: (selectedIds: string[]) => void }[] | — | Actions applied to the currently selected rows. |
renderExpandedRow | (row: Row) => ReactNode | — | Renders the expanded content for a row. |
density | 'compact' | 'normal' | 'relaxed' | 'normal' | Row density — 'compact', 'normal', or 'relaxed'. |
zebra | boolean | false | When true, applies alternating row striping. |
stickyHeader | boolean | false | When true, the header stays fixed while the body scrolls. |
loading | boolean | false | When true, shows a loading state. |
emptyState | ReactNode | — | Content shown when there are no rows. |
title | string | — | Title text for the component. |
description | string | — | Supporting description text. |
labels | DataTableLabels | — | Overrides for the component’s user-visible strings (i18n). |
className | string | — | Additional CSS class names merged onto the root element. |
Design tokens
--cascivo-color-surface--cascivo-color-bg-subtle--cascivo-color-border--cascivo-color-border-strong--cascivo-color-text--cascivo-color-text-muted--cascivo-color-accent--cascivo-font-sans--cascivo-text-sm--cascivo-text-xs--cascivo-font-semibold--cascivo-font-medium--cascivo-radius-lg--cascivo-radius-sm--cascivo-space-2--cascivo-space-3--cascivo-space-4--cascivo-data-table-max-height--cascivo-data-table-cell-gap--cascivo-duration-150--cascivo-duration-500--cascivo-ease-out--cascivo-ease-in-out
When to use
- Displaying tabular data with columns the user sorts, filters, or pages through
- Selecting rows for batch actions across a dataset
- Rendering large datasets that benefit from row containment and server-side sort/paging
When not to use
- A simple static list of items — use List
- Layout grids of cards or media — use a Card grid, not a data table
How to build an accessible DataTable in React →
Examples
Basic table
<DataTable
columns={[
{ key: 'name', header: 'Name', sortable: true },
{ key: 'role', header: 'Role' },
]}
rows={[
{ name: 'Alice', role: 'Engineer' },
{ name: 'Bob', role: 'Designer' },
]}
getRowId={(r) => r.name}
/>Custom cell content with Column.render
Use Column.render to return any ReactNode per cell — a Badge for status, an icon + link for a repo, a right-aligned timestamp. Columns without render fall back to String(row[key]).
<DataTable
columns={[
{ key: 'name', header: 'Project', sortable: true },
{
key: 'status',
header: 'Status',
render: (row) => (
<Badge variant={row.status === 'ready' ? 'success' : 'warning'}>{row.status}</Badge>
),
},
{
key: 'updated',
header: 'Updated',
align: 'end',
render: (row) => new Date(row.updated).toLocaleDateString(),
},
]}
rows={rows}
getRowId={(r) => r.name}
/>Full-featured: selection, pagination, search
<DataTable
columns={columns}
rows={rows}
getRowId={(r) => r.id}
searchable
pagination={{ pageSize: 10, pageSizeOptions: [10, 25, 50] }}
selection={{ mode: 'multi', onChange: setSelected }}
batchActions={[{ label: 'Delete', onClick: deleteRows }]}
stickyHeader
zebra
/>Related components
- Pagination — DataTable embeds pagination controls for paged data
- EmptyState — Render an EmptyState via the emptyState prop when there are no rows