The example below loads live from Storybook; open it in Storybook for the controls.
Default
Section titled “Default”DataTable needs four things: the column definitions (columns), the data (data), a stable id for each row (getRowId) and a caption (caption). The look comes from the same parts as Table.
| Status | |||
|---|---|---|---|
| Item 1 | User 1 | 12 | |
| Item 2 | User 2 | 49 | |
| Item 3 | User 3 | 86 | |
| Item 4 | User 4 | 123 | |
| Item 5 | User 5 | 160 |
import { createDataTableColumnHelper, DataTable, StatusBadge, type StatusKey } from '@bigbang/ui';
type Row = { id: string; name: string; status: StatusKey; owner: string; count: number };
const data: Row[] = [ { id: 'row-1', name: 'Item 1', status: 'active', owner: 'User 1', count: 12 }, { id: 'row-2', name: 'Item 2', status: 'pending', owner: 'User 2', count: 49 }, { id: 'row-3', name: 'Item 3', status: 'draft', owner: 'User 3', count: 86 }, { id: 'row-4', name: 'Item 4', status: 'completed', owner: 'User 4', count: 123 }, { id: 'row-5', name: 'Item 5', status: 'failed', owner: 'User 5', count: 160 },];
const helper = createDataTableColumnHelper<Row>();
const columns = [ helper.accessor('name', { header: 'Name', meta: { rowHeader: true } }), helper.accessor('status', { header: 'Status', enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }), helper.accessor('owner', { header: 'Owner' }), helper.accessor('count', { header: 'Count', meta: { numeric: true } }),];
export default function Default() { return <DataTable columns={columns} data={data} getRowId={(row) => row.id} caption="Item list" />;}Sorting
Section titled “Sorting”The header of a sortable column is a button. A click cycles ascending, descending and unsorted; aria-sort on the th reports the state and the change is announced in a polite live region. defaultSorting sets the first sorting; use sorting and onSortingChange to own the state. Add manualSorting when the server sorts.
| Status | |||
|---|---|---|---|
| Item 5 | User 5 | 160 | |
| Item 4 | User 4 | 123 | |
| Item 3 | User 3 | 86 | |
| Item 2 | User 2 | 49 | |
| Item 1 | User 1 | 12 |
import { createDataTableColumnHelper, DataTable, StatusBadge, type StatusKey } from '@bigbang/ui';
type Row = { id: string; name: string; status: StatusKey; owner: string; count: number };
const data: Row[] = [ { id: 'row-1', name: 'Item 1', status: 'active', owner: 'User 1', count: 12 }, { id: 'row-2', name: 'Item 2', status: 'pending', owner: 'User 2', count: 49 }, { id: 'row-3', name: 'Item 3', status: 'draft', owner: 'User 3', count: 86 }, { id: 'row-4', name: 'Item 4', status: 'completed', owner: 'User 4', count: 123 }, { id: 'row-5', name: 'Item 5', status: 'failed', owner: 'User 5', count: 160 },];
const helper = createDataTableColumnHelper<Row>();
const columns = [ helper.accessor('name', { header: 'Name', meta: { rowHeader: true } }), helper.accessor('status', { header: 'Status', enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }), helper.accessor('owner', { header: 'Owner' }), helper.accessor('count', { header: 'Count', meta: { numeric: true } }),];
export default function Sorting() { return <DataTable columns={columns} data={data} getRowId={(row) => row.id} caption="Item list" defaultSorting={[{ id: 'count', desc: true }]} />;}Selection
Section titled “Selection”selectionMode="multiple" adds a checkbox column and a select-all checkbox; the checkbox shows a mixed state while some rows are selected. Row checkboxes carry the row’s name through getRowLabel: “Select row Item 1”. single allows one row at a time.
| Status | ||||
|---|---|---|---|---|
| Item 1 | User 1 | 12 | ||
| Item 2 | User 2 | 49 | ||
| Item 3 | User 3 | 86 | ||
| Item 4 | User 4 | 123 | ||
| Item 5 | User 5 | 160 |
import { createDataTableColumnHelper, DataTable, StatusBadge, type StatusKey } from '@bigbang/ui';
type Row = { id: string; name: string; status: StatusKey; owner: string; count: number };
const data: Row[] = [ { id: 'row-1', name: 'Item 1', status: 'active', owner: 'User 1', count: 12 }, { id: 'row-2', name: 'Item 2', status: 'pending', owner: 'User 2', count: 49 }, { id: 'row-3', name: 'Item 3', status: 'draft', owner: 'User 3', count: 86 }, { id: 'row-4', name: 'Item 4', status: 'completed', owner: 'User 4', count: 123 }, { id: 'row-5', name: 'Item 5', status: 'failed', owner: 'User 5', count: 160 },];
const helper = createDataTableColumnHelper<Row>();
const columns = [ helper.accessor('name', { header: 'Name', meta: { rowHeader: true } }), helper.accessor('status', { header: 'Status', enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }), helper.accessor('owner', { header: 'Owner' }), helper.accessor('count', { header: 'Count', meta: { numeric: true } }),];
export default function Selection() { return <DataTable columns={columns} data={data} getRowId={(row) => row.id} getRowLabel={(row) => row.name} caption="Item list" selectionMode="multiple" defaultRowSelection={{ 'row-2': true }} />;}Column visibility
Section titled “Column visibility”columnToggle adds a “Columns” button above the table; it opens a panel with a checkbox for every column. defaultColumnVisibility sets the starting state; columnVisibility and onColumnVisibilityChange control it.
| Status | ||
|---|---|---|
| Item 1 | 12 | |
| Item 2 | 49 | |
| Item 3 | 86 | |
| Item 4 | 123 | |
| Item 5 | 160 |
import { createDataTableColumnHelper, DataTable, StatusBadge, type StatusKey } from '@bigbang/ui';
type Row = { id: string; name: string; status: StatusKey; owner: string; count: number };
const data: Row[] = [ { id: 'row-1', name: 'Item 1', status: 'active', owner: 'User 1', count: 12 }, { id: 'row-2', name: 'Item 2', status: 'pending', owner: 'User 2', count: 49 }, { id: 'row-3', name: 'Item 3', status: 'draft', owner: 'User 3', count: 86 }, { id: 'row-4', name: 'Item 4', status: 'completed', owner: 'User 4', count: 123 }, { id: 'row-5', name: 'Item 5', status: 'failed', owner: 'User 5', count: 160 },];
const helper = createDataTableColumnHelper<Row>();
const columns = [ helper.accessor('name', { header: 'Name', meta: { rowHeader: true } }), helper.accessor('status', { header: 'Status', enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }), helper.accessor('owner', { header: 'Owner' }), helper.accessor('count', { header: 'Count', meta: { numeric: true } }),];
export default function ColumnVisibility() { return <DataTable columns={columns} data={data} getRowId={(row) => row.id} caption="Item list" columnToggle defaultColumnVisibility={{ owner: false }} />;}States
Section titled “States”status shows loading, empty, filtered-empty and error states built in; the header always stays in place. While loading the table is aria-busy and shows skeleton rows. The empty and error messages can carry an action button.
| Status | |||
|---|---|---|---|
| Status | |||
|---|---|---|---|
Filtrelere uyan kayıt yok | |||
| Status | |||
|---|---|---|---|
Veriler yüklenemedi | |||
import { createDataTableColumnHelper, DataTable, StatusBadge, type StatusKey } from '@bigbang/ui';
type Row = { id: string; name: string; status: StatusKey; owner: string; count: number };
const helper = createDataTableColumnHelper<Row>();
const columns = [ helper.accessor('name', { header: 'Name', meta: { rowHeader: true } }), helper.accessor('status', { header: 'Status', enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }), helper.accessor('owner', { header: 'Owner' }), helper.accessor('count', { header: 'Count', meta: { numeric: true } }),];
export default function States() { return ( <div className="flex w-full flex-col gap-3xl"> <DataTable columns={columns} data={[]} getRowId={(row) => row.id} caption="Item list: loading" status="loading" /> <DataTable columns={columns} data={[]} getRowId={(row) => row.id} caption="Item list: filtered" status="filtered-empty" onClearFilters={() => {}} /> <DataTable columns={columns} data={[]} getRowId={(row) => row.id} caption="Item list: error" status="error" onRetry={() => {}} /> </div> );}Row actions and opening a row
Section titled “Row actions and opening a row”rowActions shows the row’s actions in the last column; each action is its own tab stop and its name should say which row it belongs to. onRowOpen turns the first column’s content into a button, so a row opens with the keyboard and not only by click.
| Status | İşlemler | |||
|---|---|---|---|---|
| User 1 | 12 | |||
| User 2 | 49 | |||
| User 3 | 86 | |||
| User 4 | 123 | |||
| User 5 | 160 |
import { createDataTableColumnHelper, DataTable, StatusBadge, type StatusKey, IconButton } from '@bigbang/ui';
type Row = { id: string; name: string; status: StatusKey; owner: string; count: number };
const data: Row[] = [ { id: 'row-1', name: 'Item 1', status: 'active', owner: 'User 1', count: 12 }, { id: 'row-2', name: 'Item 2', status: 'pending', owner: 'User 2', count: 49 }, { id: 'row-3', name: 'Item 3', status: 'draft', owner: 'User 3', count: 86 }, { id: 'row-4', name: 'Item 4', status: 'completed', owner: 'User 4', count: 123 }, { id: 'row-5', name: 'Item 5', status: 'failed', owner: 'User 5', count: 160 },];
const helper = createDataTableColumnHelper<Row>();
const columns = [ helper.accessor('name', { header: 'Name', meta: { rowHeader: true } }), helper.accessor('status', { header: 'Status', enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }), helper.accessor('owner', { header: 'Owner' }), helper.accessor('count', { header: 'Count', meta: { numeric: true } }),];
export default function RowActions() { return ( <DataTable columns={columns} data={data} getRowId={(row) => row.id} caption="Item list" onRowOpen={() => {}} rowActions={(row) => ( <> <IconButton icon="pencil" size="sm" label={`Edit: ${row.name}`} /> <IconButton icon="trash" size="sm" label={`Delete: ${row.name}`} /> </> )} /> );}Footer
Section titled “Footer”footer opens a row below the table. Put the pagination component there; DataTable does not paginate by itself. When the data is one page, report the total row count with totalRowCount.
| Status | |||
|---|---|---|---|
| Item 1 | User 1 | 12 | |
| Item 2 | User 2 | 49 |
import { createDataTableColumnHelper, DataTable, StatusBadge, type StatusKey, Button } from '@bigbang/ui';import { useState } from 'react';
type Row = { id: string; name: string; status: StatusKey; owner: string; count: number };
const data: Row[] = [ { id: 'row-1', name: 'Item 1', status: 'active', owner: 'User 1', count: 12 }, { id: 'row-2', name: 'Item 2', status: 'pending', owner: 'User 2', count: 49 }, { id: 'row-3', name: 'Item 3', status: 'draft', owner: 'User 3', count: 86 }, { id: 'row-4', name: 'Item 4', status: 'completed', owner: 'User 4', count: 123 }, { id: 'row-5', name: 'Item 5', status: 'failed', owner: 'User 5', count: 160 },];
const helper = createDataTableColumnHelper<Row>();
const columns = [ helper.accessor('name', { header: 'Name', meta: { rowHeader: true } }), helper.accessor('status', { header: 'Status', enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }), helper.accessor('owner', { header: 'Owner' }), helper.accessor('count', { header: 'Count', meta: { numeric: true } }),];
export default function WithFooter() { const [page, setPage] = useState(0); const rows = data.slice(page * 2, page * 2 + 2); return ( <DataTable columns={columns} data={rows} getRowId={(row) => row.id} caption="Item list" totalRowCount={data.length} footer={ <> <span className="type-body-sm text-fg-subtle">Page {page + 1} / 3</span> <span className="flex items-center gap-sm"> <Button size="sm" variant="secondary" disabled={page === 0} onClick={() => setPage(page - 1)}> Previous </Button> <Button size="sm" variant="secondary" disabled={page === 2} onClick={() => setPage(page + 1)}> Next </Button> </span> </> } /> );}Large data
Section titled “Large data”virtualize renders only the rows in view. It needs a scroll container with a fixed height (scrollClassName="max-h-96"); the 5,000-row example runs in Storybook.
Pinned columns
Section titled “Pinned columns”meta: { pin: 'start' } or meta: { pin: 'end' } in a column definition keeps that column in place while the table scrolls sideways. Pin the name column that identifies the row to the start and a compared column such as an amount to the end. With a start-pinned column, the selection and expand columns are pinned too; a thin line marks the edge of the pinned area.
Expandable rows
Section titled “Expandable rows”renderExpanded adds an expand button at the start of each row and shows a full-width detail row under an open row. Leave out rows that cannot open with getRowCanExpand; manage the open rows with expanded and onExpandedChange.
Light and dark mode
Section titled “Light and dark mode”DataTable takes its colours from the same table.* tokens as Table; it keeps the same structure in both modes.
| Status | |||
|---|---|---|---|
| Item 1 | User 1 | 12 | |
| Item 2 | User 2 | 49 | |
| Item 3 | User 3 | 86 | |
| Item 4 | User 4 | 123 | |
| Item 5 | User 5 | 160 |
| Status | |||
|---|---|---|---|
| Item 1 | User 1 | 12 | |
| Item 2 | User 2 | 49 | |
| Item 3 | User 3 | 86 | |
| Item 4 | User 4 | 123 | |
| Item 5 | User 5 | 160 |
import { createDataTableColumnHelper, DataTable, StatusBadge, type StatusKey } from '@bigbang/ui';
type Row = { id: string; name: string; status: StatusKey; owner: string; count: number };
const data: Row[] = [ { id: 'row-1', name: 'Item 1', status: 'active', owner: 'User 1', count: 12 }, { id: 'row-2', name: 'Item 2', status: 'pending', owner: 'User 2', count: 49 }, { id: 'row-3', name: 'Item 3', status: 'draft', owner: 'User 3', count: 86 }, { id: 'row-4', name: 'Item 4', status: 'completed', owner: 'User 4', count: 123 }, { id: 'row-5', name: 'Item 5', status: 'failed', owner: 'User 5', count: 160 },];
const helper = createDataTableColumnHelper<Row>();
const columns = [ helper.accessor('name', { header: 'Name', meta: { rowHeader: true } }), helper.accessor('status', { header: 'Status', enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }), helper.accessor('owner', { header: 'Owner' }), helper.accessor('count', { header: 'Count', meta: { numeric: true } }),];
export default function Default() { return <DataTable columns={columns} data={data} getRowId={(row) => row.id} caption="Item list" />;}