Data Table
DataTable renders a list of records. Columns are TanStack Table
column definitions; sorting and selection are built in. It does not page: pair it with
Pagination.
import { DataTable, createColumnHelper, type ColumnDef } from '@beyul/sulmu-ui'Examples
Section titled “Examples”Click a column header to sort. Set enableSorting: false on a column to opt out.
| User Type | |||
|---|---|---|---|
JM Jane Menefee | Active | jane@beyul.com | Member |
SJ Sandra Johnson | Active | sandra@beyul.com | Admin |
RS Ruben Stanton | Suspended | ruben@beyul.com | Member |
PB Phillip Botosh | Active | phillip@beyul.com | Member |
import { Avatar, DataTable, Flex, Tag, type ColumnDef } from '@beyul/sulmu-ui'
type User = { id: string name: string status: 'Active' | 'Suspended' email: string type: string}
const users: User[] = [ { id: '1', name: 'Jane Menefee', status: 'Active', email: 'jane@beyul.com', type: 'Member' }, { id: '2', name: 'Sandra Johnson', status: 'Active', email: 'sandra@beyul.com', type: 'Admin' }, { id: '3', name: 'Ruben Stanton', status: 'Suspended', email: 'ruben@beyul.com', type: 'Member' }, { id: '4', name: 'Phillip Botosh', status: 'Active', email: 'phillip@beyul.com', type: 'Member' },]
const columns: ColumnDef<User>[] = [ { accessorKey: 'name', header: 'Full Name', cell: ({ row }) => ( <Flex inline align="center" gap={2}> <Avatar size="xs" name={row.original.name} /> {row.original.name} </Flex> ), }, { accessorKey: 'status', header: 'Status', cell: ({ row }) => ( <Tag tone={row.original.status === 'Active' ? 'green' : 'orange'}>{row.original.status}</Tag> ), }, { accessorKey: 'email', header: 'Email' }, { accessorKey: 'type', header: 'User Type', enableSorting: false },]
export default function Example() { return <DataTable aria-label="Users" columns={columns} data={users} getRowId={(u) => u.id} />}Selectable
Section titled “Selectable”selectable adds a checkbox column. Selection is { [rowId]: true }; pass getRowId so it
survives sorting and refetching.
| Jane Menefee | jane@beyul.com | |
| Sandra Johnson | sandra@beyul.com | |
| Ruben Stanton | ruben@beyul.com |
Selected ids: none
import { DataTable, Stack, Text, type ColumnDef, type RowSelectionState } from '@beyul/sulmu-ui'import { useState } from 'react'
type User = { id: string; name: string; email: string }
const users: User[] = [ { id: '1', name: 'Jane Menefee', email: 'jane@beyul.com' }, { id: '2', name: 'Sandra Johnson', email: 'sandra@beyul.com' }, { id: '3', name: 'Ruben Stanton', email: 'ruben@beyul.com' },]
const columns: ColumnDef<User>[] = [ { accessorKey: 'name', header: 'Full Name' }, { accessorKey: 'email', header: 'Email' },]
export default function Example() { const [selection, setSelection] = useState<RowSelectionState>({}) return ( <Stack gap={3}> <DataTable aria-label="Users" columns={columns} data={users} getRowId={(u) => u.id} selectable rowSelection={selection} onRowSelectionChange={setSelection} /> <Text textStyle="caption" tone="subtle"> Selected ids: {Object.keys(selection).join(', ') || 'none'} </Text> </Stack> )}empty is shown when data is empty. The default is the text “Nothing to show”.
No users yetInvite someone to get started. | |
import { Button, DataTable, EmptyState, type ColumnDef } from '@beyul/sulmu-ui'
type User = { id: string; name: string; email: string }
const columns: ColumnDef<User>[] = [ { accessorKey: 'name', header: 'Full Name' }, { accessorKey: 'email', header: 'Email' },]
export default function Example() { return ( <DataTable aria-label="Users" columns={columns} data={[]} empty={ <EmptyState title="No users yet" description="Invite someone to get started." actions={<Button size="sm">Invite user</Button>} /> } /> )}| Prop | Type | Default | Description |
|---|---|---|---|
columns* | ColumnDef<T, any>[] | - | Column definitions (TanStack Table). Set enableSorting: false on a column to opt out. |
data* | T[] | - | |
getRowId | ((row: T, index: number) => string) | - | Stable id for each row. Needed for selection to survive sorting and refetching. |
aria-label* | string | - | Name of the table for assistive tech, e.g. "Users". |
selectable | boolean | - | Add a checkbox column. |
rowSelection | RowSelectionState | - | Controlled selection: { [rowId]: true }. Leave out to let the table manage it. |
onRowSelectionChange | OnChangeFn<RowSelectionState> | - | |
sorting | SortingState | - | Controlled sorting. Leave out to let the table manage it. |
onSortingChange | OnChangeFn<SortingState> | - | |
manualSorting | boolean | - | Sort on the server: the table only shows the sort state and reports changes. |
striped | boolean | true | Alternate row backgrounds. Default: true. |
empty | ReactNode | Nothing to show | Shown when data is empty. |
className | string | - |
Accessibility and usage
Section titled “Accessibility and usage”aria-labelis required: it names the table (“Users”).- Sortable headers are buttons, and the sorted column carries
aria-sort. - The header checkbox is labelled “Select all rows” and shows the indeterminate state when only some rows are selected.
- Leave out
sortingandrowSelectionto let the table manage its own state. Pass them with their change handlers to control it. - With
manualSortingthe table only shows the sort state and reports changes: sort on the server and pass the sorteddata. - A column’s
sizesets its width. ColumnDef,RowSelectionState,SortingStateandcreateColumnHelperare re-exported, so the app does not need to import from@tanstack/react-table.