Skip to content

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'

Source · Figma

Click a column header to sort. Set enableSorting: false on a column to opt out.

User Type
JM
Jane Menefee
Activejane@beyul.comMember
SJ
Sandra Johnson
Activesandra@beyul.comAdmin
RS
Ruben Stanton
Suspendedruben@beyul.comMember
PB
Phillip Botosh
Activephillip@beyul.comMember
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 adds a checkbox column. Selection is { [rowId]: true }; pass getRowId so it survives sorting and refetching.

Jane Menefeejane@beyul.com
Sandra Johnsonsandra@beyul.com
Ruben Stantonruben@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 yet

Invite 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>}
/>
}
/>
)
}
PropTypeDefaultDescription
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".
selectableboolean-Add a checkbox column.
rowSelectionRowSelectionState-Controlled selection: { [rowId]: true }. Leave out to let the table manage it.
onRowSelectionChangeOnChangeFn<RowSelectionState>-
sortingSortingState-Controlled sorting. Leave out to let the table manage it.
onSortingChangeOnChangeFn<SortingState>-
manualSortingboolean-Sort on the server: the table only shows the sort state and reports changes.
stripedbooleantrueAlternate row backgrounds. Default: true.
emptyReactNodeNothing to showShown when data is empty.
classNamestring-
  • aria-label is 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 sorting and rowSelection to let the table manage its own state. Pass them with their change handlers to control it.
  • With manualSorting the table only shows the sort state and reports changes: sort on the server and pass the sorted data.
  • A column’s size sets its width.
  • ColumnDef, RowSelectionState, SortingState and createColumnHelper are re-exported, so the app does not need to import from @tanstack/react-table.