Skip to content

Data display

Data table

Data table is a client-side table for real lists: global search, sortable columns, row selection with bulk actions, a Columns menu to show and hide columns, pagination and a helpful empty state. You describe columns once (how to read, sort and render each value) and the table does the rest. On small screens it scrolls sideways inside its frame while the toolbar and pagination wrap.

On this page
Invoices, sorted by Issued descending
INV-2078Copperleaf DentalPaidSep 20, 2026$13,243.94
INV-2073Lumen AtelierPendingSep 19, 2026$1,648.21
INV-2068Sundial HealthPaidSep 18, 2026$4,053.45
INV-2063Brindle Coffee Co.PendingSep 17, 2026$6,458.69
INV-2058QuillmarkPaidSep 16, 2026$8,863.93
INV-2053Halden WorksPendingSep 15, 2026$11,268.20
INV-2048Marlow & FinchPaidSep 14, 2026$13,673.44
INV-2043Vantor FreightPendingSep 13, 2026$2,078.68
INV-2080Kettle & LoomDraftSep 2, 2026$1,081.65
INV-2075Tidewell LogisticsPendingSep 1, 2026$3,486.89

1–10 of 40 invoices

Usage

components/ui/data-table.tsx
import { DataTable, type DataTableColumn } from "@/components/ui/data-table"; const columns: DataTableColumn<Invoice>[] = [  { id: "id", header: "Invoice", accessor: (r) => r.id },  { id: "amount", header: "Amount", accessor: (r) => r.amount, numeric: true, cell: (r) => usd(r.amount) },]; <DataTable label="Invoices" data={invoices} columns={columns} getRowId={(r) => r.id} selectable />

Examples

Overview

40 invoices with search, sorting (newest first), selection with bulk actions, Email and Due columns hidden in the Columns menu, and pagination. Select a few rows and mark them as paid.

Invoices, sorted by Issued descending
INV-2078Copperleaf DentalPaidSep 20, 2026$13,243.94
INV-2073Lumen AtelierPendingSep 19, 2026$1,648.21
INV-2068Sundial HealthPaidSep 18, 2026$4,053.45
INV-2063Brindle Coffee Co.PendingSep 17, 2026$6,458.69
INV-2058QuillmarkPaidSep 16, 2026$8,863.93
INV-2053Halden WorksPendingSep 15, 2026$11,268.20
INV-2048Marlow & FinchPaidSep 14, 2026$13,673.44
INV-2043Vantor FreightPendingSep 13, 2026$2,078.68
INV-2080Kettle & LoomDraftSep 2, 2026$1,081.65
INV-2075Tidewell LogisticsPendingSep 1, 2026$3,486.89

1–10 of 40 invoices

Defining columns

accessor returns the raw value used for sorting and search (strings, numbers, dates); cell renders it. numeric right-aligns with tabular figures, searchable: false leaves a column out of the search, defaultHidden starts it hidden and hideable: false keeps it visible. This list sorts by last activity and shows five rows per page.

Team members, sorted by Last active ascending
Ada Morenoada@ledgerly.exampleOwner32 min ago
Felix Brandtfelix@ledgerly.exampleMember215 min ago
Theo Lindqvisttheo@ledgerly.exampleAdmin245 min ago
Priya Ramanpriya@ledgerly.exampleMember13 h ago
Sana Ibrahimsana@ledgerly.exampleAdmin110 h ago

1–5 of 7 members

Empty state

emptyState shows when there is no data at all. When a search has no matches the table shows its own message with a Clear search button.

Invoices

No invoices yet

Invoices you create or import will be listed here.

No invoices

Props

DataTable

  • datarequiredT[]default: —Rows.
  • columnsrequiredDataTableColumn<T>[]default: —Column definitions (see below).
  • getRowIdrequired(row: T) => stringdefault: —Stable id per row, used for selection and keys.
  • labelrequiredstringdefault: —Accessible name of the table, e.g. "Invoices".
  • rowNounstringdefault: "row"Used in counts and checkbox labels ("3 invoices selected").
  • selectablebooleandefault: falseAdds a checkbox column and the selection bar.
  • selected / onSelectionChangestring[] / (ids) => voiddefault: —Controlled selection, or just listen to changes.
  • bulkActions(rows: T[], clear: () => void) => ReactNodedefault: —Actions shown in the selection bar.
  • searchablebooleandefault: trueShow the global search field.
  • searchPlaceholderstringdefault: "Search…"Placeholder of the search field.
  • initialSort{ id: string; direction: "asc" | "desc" } | nulldefault: nullStarting sort.
  • pageSize / pageSizeOptionsnumber / number[]default: 10 / [10, 20, 50]Rows per page and the choices offered.
  • toolbarReactNodedefault: —Extra controls next to the Columns menu.
  • emptyStateReactNodedefault: —Shown when data is empty.

DataTableColumn<T>

  • id / headerrequiredstringdefault: —Unique id and header text.
  • accessor(row: T) => string | number | Date | boolean | nulldefault: —Raw value for sorting and search.
  • cell(row: T) => ReactNodedefault: —Custom rendering. Defaults to the accessor value.
  • sortablebooleandefault: true with accessorAllow sorting by this column.
  • searchablebooleandefault: true for text and numbersInclude in the global search.
  • numericbooleandefault: falseRight-aligned tabular figures.
  • align"left" | "center" | "right"default: "left"Cell alignment.
  • hideable / defaultHiddenbooleandefault: true / falseColumns menu behavior.
  • width / classNamenumber | string / stringdefault: —Column width and extra cell classes.

Accessibility

  • Sortable headers are buttons inside the th, and the th carries aria-sort ("ascending", "descending" or "none"); the caption also states the current sort.
  • Every checkbox has a label ("Select invoice INV-2041", "Select all invoices on this page"), and the header checkbox shows a mixed state when only some rows are selected.
  • The Columns menu follows the menu button pattern: Enter, Space or Down opens it, arrows move, Home and End jump, Escape closes and returns focus; items are menuitemcheckbox with aria-checked.
  • The search field has a label, and the result count is a polite live region, so filtering is announced.
  • Pagination is a labelled nav with named icon buttons; the scroll area is a focusable region so keyboard users can scroll wide tables.

Suggested