Skip to content
BigBang

Popular

    ↑↓Navigate↵OpenEscClose
    TR

    Components

    DataTable

    Shows large or changing data in a table with sorting, selection, column visibility and virtualisation.

    Storybook
    Storybook
    Source
    Source

    The example below loads live from Storybook; open it in Storybook for the controls.

    Live example, StorybookOpen in Storybook

    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.

    Item list
    Status
    Item 1EtkinUser 112
    Item 2BeklemedeUser 249
    Item 3TaslakUser 386
    Item 4TamamlandıUser 4123
    Item 5BaşarısızUser 5160
    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" />;
    }

    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.

    Item list
    Status
    Item 5BaşarısızUser 5160
    Item 4TamamlandıUser 4123
    Item 3TaslakUser 386
    Item 2BeklemedeUser 249
    Item 1EtkinUser 112
    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 }]} />;
    }

    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.

    Item list
    Status
    Item 1EtkinUser 112
    Item 2BeklemedeUser 249
    Item 3TaslakUser 386
    Item 4TamamlandıUser 4123
    Item 5BaşarısızUser 5160
    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 }} />;
    }

    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.

    Item list
    Status
    Item 1Etkin12
    Item 2Beklemede49
    Item 3Taslak86
    Item 4Tamamlandı123
    Item 5Başarısız160
    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 }} />;
    }

    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.

    Item list: loading
    Status
    Yükleniyor
    Item list: filtered
    Status

    Filtrelere uyan kayıt yok

    Item list: error
    Status
    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>
    );
    }

    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.

    Item list
    Statusİşlemler
    EtkinUser 112
    BeklemedeUser 249
    TaslakUser 386
    TamamlandıUser 4123
    BaşarısızUser 5160
    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 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.

    Item list
    Status
    Item 1EtkinUser 112
    Item 2BeklemedeUser 249
    Page 1 / 3
    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>
    </>
    }
    />
    );
    }

    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.

    Live example, StorybookOpen in Storybook

    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.

    Live example, StorybookOpen in Storybook

    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.

    Live example, StorybookOpen in Storybook

    DataTable takes its colours from the same table.* tokens as Table; it keeps the same structure in both modes.

    Item list
    Status
    Item 1EtkinUser 112
    Item 2BeklemedeUser 249
    Item 3TaslakUser 386
    Item 4TamamlandıUser 4123
    Item 5BaşarısızUser 5160
    Item list
    Status
    Item 1EtkinUser 112
    Item 2BeklemedeUser 249
    Item 3TaslakUser 386
    Item 4TamamlandıUser 4123
    Item 5BaşarısızUser 5160
    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" />;
    }