Components
DataTable
DataTable is a feature-rich data table with built-in sorting, filtering, pagination, column visibility, and row selection.
DataTable is built on TanStack Table and should be preferred over the lower-level Table component for any use case involving dynamic data. Table is a dormant component for styled HTML table elements only.
() => {const data = [{ name: 'Alice', role: 'Engineer', department: 'Platform', active: true },{ name: 'Bob', role: 'Designer', department: 'Product', active: true },{ name: 'Charlie', role: 'Manager', department: 'Platform', active: false },{ name: 'Diana', role: 'Analyst', department: 'Data', active: true },{ name: 'Eve', role: 'Developer', department: 'Product', active: true },]const columns = [{ key: 'name', label: 'Name', isRowHeader: true },{ key: 'role', label: 'Role' },{ key: 'department', label: 'Department' },{key: 'active',label: 'Status',renderCell: (value) => (<Badge badgeColor={value ? 'green' : 'red'}>{value ? 'Active' : 'Inactive'}</Badge>),},]return (<DataTabledata={data}columns={columns}caption="Team Members"emptyMessage="No team members found"/>)}
Properties
| Name | Type | Default | Description | Required? |
|---|
Sorting
All columns are sortable by default — click a header to sort ascending, again for descending, again to clear. Disable sorting globally with enableSorting={false} or per-column with disableSorting: true.
Use sortingFn on a column to provide a custom comparator. Write it for ascending order; TanStack Table handles direction automatically. Use sortDescFirst: true to make the first click sort descending — useful for numeric columns where highest-first is more natural.
() => {const [sorting, setSorting] = useState([{ id: 'name', desc: false }])const data = [{ name: 'Charlie', score: 92, department: 'Engineering' },{ name: 'Alice', score: 88, department: 'Design' },{ name: 'Bob', score: 95, department: 'Engineering' },{ name: 'Diana', score: 79, department: 'Marketing' },]const columns = [{ key: 'name', label: 'Name' },{key: 'score',label: 'Score',sortDescFirst: true,renderCell: (value) => `${value}/100`,},{ key: 'department', label: 'Department', disableSorting: true },]return (<DataTabledata={data}columns={columns}caption="Team Scores"emptyMessage="No data"state={{ sorting }}onSortingChange={setSorting}/>)}
Custom Cell Rendering
Use renderCell on a column to control how each cell renders. It receives the raw cell value and the full row object. Set isRowHeader: true to render a column's cells as <th scope="row"> — use this for the column that identifies each row.
() => {const data = [{ name: 'Alice', email: 'alice@example.com', score: 94 },{ name: 'Bob', email: 'bob@example.com', score: 72 },{ name: 'Charlie', email: 'charlie@example.com', score: 85 },]const columns = [{ key: 'name', label: 'Name', isRowHeader: true },{key: 'email',label: 'Email',renderCell: (value) => (<a href={`mailto:${value}`}>{value}</a>),},{key: 'score',label: 'Score',sortDescFirst: true,renderCell: (value) => {const color = value >= 90 ? 'green' : value >= 80 ? 'yellow' : 'red'return <Badge badgeColor={color}>{value}/100</Badge>},},]return (<DataTabledata={data}columns={columns}caption="User Scores"emptyMessage="No users found"/>)}
Simple Pagination (Show More / Show Less)
Use simplePaginationConfig to progressively reveal rows. Best for compact tables where all data is already loaded client-side.
() => {const data = Array.from({ length: 20 }, (_, i) => ({id: i + 1,name: `User ${i + 1}`,department: ['Engineering', 'Design', 'Marketing', 'Sales'][i % 4],}))const columns = [{ key: 'id', label: 'ID' },{ key: 'name', label: 'Name' },{ key: 'department', label: 'Department' },]return (<DataTabledata={data}columns={columns}caption="Users"emptyMessage="No users"simplePaginationConfig={{initialVisibleRows: 5,expandBy: 5,showMoreText: 'Show More',showLessText: 'Show Less',displayShowAllButton: true,showAllText: 'Show All',}}/>)}
Full Pagination
Use paginationConfig for page-based navigation with a page size selector. Sorting operates across the full dataset, not just the current page. simplePaginationConfig and paginationConfig are mutually exclusive.
() => { const data = Array.from({ length: 50 }, (_, i) => ({ name: `User ${i + 1}`, role: ['Engineer', 'Designer', 'Manager', 'Analyst'][i % 4], score: 60 + (i % 40), }))
const columns = [ { key: 'name', label: 'Name' }, { key: 'role', label: 'Role' }, { key: 'score', label: 'Score', sortDescFirst: true }, ]
return ( <DataTable data={data} columns={columns} caption="All Users" emptyMessage="No users" paginationConfig={{ initialPageSize: 10, displayType: 'dropdown', pageSizeOptions: [10, 25, 50], }} /> )}
Search / Filtering (Experimental)
Pass a string to _experimentalSearch to filter rows across all columns. This API is experimental and may change.
The default mode is "fuzzy" — it uses match-sorter for tolerant matching. Use _experimentalSearchFilterFn="includesString" for exact substring matching. When the search value changes, pagination resets to page 1. Exclude specific columns with disableGlobalFilter: true on the column definition.
() => {const [search, setSearch] = useState('')const data = [{ name: 'Alice Chen', role: 'Engineer', location: 'Chicago' },{ name: 'Bob Smith', role: 'Designer', location: 'New York' },{ name: 'Charlie Park', role: 'Manager', location: 'Austin' },{ name: 'Diana Lee', role: 'Analyst', location: 'Remote' },{ name: 'Eve Johnson', role: 'Engineer', location: 'San Francisco' },]const columns = [{ key: 'name', label: 'Name' },{ key: 'role', label: 'Role' },{ key: 'location', label: 'Location' },]return (<Box><Box mb={300}><Inputvalue={search}onChange={(e) => setSearch(e.target.value)}placeholder="Search..."/></Box><DataTabledata={data}columns={columns}caption="Team Directory"emptyMessage="No results found"_experimentalSearch={search}/></Box>)}
Column Visibility
Control which columns render using state.columnVisibility. Columns not listed default to visible.
() => {const [columnVisibility, setColumnVisibility] = useState({ email: false })const data = [{ name: 'Alice', role: 'Engineer', email: 'alice@example.com', score: 94 },{ name: 'Bob', role: 'Designer', email: 'bob@example.com', score: 81 },{ name: 'Charlie', role: 'Manager', email: 'charlie@example.com', score: 76 },]const columns = [{ key: 'name', label: 'Name' },{ key: 'role', label: 'Role' },{ key: 'email', label: 'Email' },{ key: 'score', label: 'Score' },]return (<Box><Box mb={300}><ButtononClick={() =>setColumnVisibility((prev) => ({ ...prev, email: !prev.email }))}>{columnVisibility.email === false ? 'Show Email' : 'Hide Email'}</Button></Box><DataTabledata={data}columns={columns}caption="Team Members"emptyMessage="No data"state={{ columnVisibility }}onColumnVisibilityChange={setColumnVisibility}/></Box>)}
Fixed Layout
Set fixedLayout to apply table-layout: fixed. This prevents columns from shifting when paginating or when cell content varies in length. Use size on each column to set widths in pixels; columns without a size share remaining space equally.
() => {const data = [{ name: 'Alice', role: 'Software Engineer', notes: 'Very long notes that would normally push the column wider than expected in a fluid layout.' },{ name: 'Bob', role: 'Designer', notes: 'Short notes.' },{ name: 'Charlie', role: 'Project Manager', notes: 'Medium length notes for this row.' },]const columns = [{ key: 'name', label: 'Name', size: 120 },{ key: 'role', label: 'Role', size: 180 },{ key: 'notes', label: 'Notes' },]return (<DataTabledata={data}columns={columns}caption="Team with Fixed Layout"emptyMessage="No data"fixedLayout/>)}
Server-Side Sorting and Pagination
For data loaded from a server, manage state externally and pass it via state. Set serverSideSorting={true} so the table does not sort rows client-side. Set state.pagination.totalRows to the full dataset count so the pagination component can calculate page counts correctly.
() => { const [sorting, setSorting] = useState([]) const [pageIndex, setPageIndex] = useState(0) const pageSize = 5
const allRows = Array.from({ length: 23 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, score: 100 - i, }))
const sorted = [...allRows].sort((a, b) => { if (!sorting.length) return 0 const { id, desc } = sorting[0] const dir = desc ? -1 : 1 return a[id] > b[id] ? dir : a[id] < b[id] ? -dir : 0 })
const pageRows = sorted.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize)
const columns = [ { key: 'id', label: 'ID' }, { key: 'name', label: 'Name' }, { key: 'score', label: 'Score', sortDescFirst: true }, ]
return ( <DataTable data={pageRows} columns={columns} caption="Server-Side Table" emptyMessage="No data" serverSideSorting state={{ sorting, pagination: { pageIndex, pageSize, totalRows: allRows.length }, }} onSortingChange={setSorting} onPageIndexChange={setPageIndex} paginationConfig={{ initialPageSize: pageSize, displayType: 'dropdown', pageSizeOptions: [5, 10, 25], }} /> )}
Custom Caption
Pass any data structure to caption and use renderCaption to control how it renders. This is useful for pairing the table title with badges or other metadata.
() => {const data = [{ name: 'Alice', role: 'Engineer', department: 'Platform' },{ name: 'Bob', role: 'Designer', department: 'Product' },{ name: 'Charlie', role: 'Manager', department: 'Platform' },]const columns = [{ key: 'name', label: 'Name' },{ key: 'role', label: 'Role' },{ key: 'department', label: 'Department' },]return (<DataTabledata={data}columns={columns}caption={{ title: 'Team Members', count: 3 }}renderCaption={(cap) => (<Box display="flex" alignItems="center"><Text mr={200} fontWeight="semibold">{cap.title}</Text><Badge>{cap.count}</Badge></Box>)}emptyMessage="No members"/>)}
Accessibility
captionis required. It labels the table for screen readers. UsedisplayCaption={false}only when the table's purpose is already clear from context.- Use
isRowHeader: trueon the column that uniquely identifies each row (e.g. name or ID) — it renders as<th scope="row">. - Sort state is communicated via
aria-sorton header cells.
Best Practices
- Prefer DataTable over the dormant Table component for any dynamic data use case.
- Always provide a meaningful
captioneven whendisplayCaption={false}. - Use
fixedLayoutwhen combining pagination with defined column widths to prevent layout shifts between pages. - Use
simplePaginationConfigfor short client-side lists; usepaginationConfigfor larger datasets or server-side pagination. _experimentalSearchis experimental — its API may change. For server-side search, filter your data before passing it to DataTable.