DataTable

Interactive table — sort, select, expand, and click through rows of data.

import { DataTable } from "@usefragments/ui";Source

Preview

<DataTable
  columns={[
    { accessorKey: "name", header: "Name" },
    { accessorKey: "status", header: "Status" },
  ]}
  data={[

Usage

Use when

  • Rows need sorting, selection, or expansion
  • Users scan and compare several attributes per row
  • A row click opens the record behind it

Avoid when

  • The data is read-only and static (use Table)
  • Each item is a single line (use List)
  • Narrow screens where a card list reads better

Implementation notes

  • Cap visible columns near 5–7; push the rest behind a row click
  • Right-align numbers, left-align text
  • Always set emptyState — a bare grid reads as broken
  • Pass error and onRetry for a failed load; loading wins over error, error over empty
  • Clickable rows need getRowProps for a role and an accessible name
  • Every row is the 32px control track; there is no density or stripe option

Do not use DataTable for a simple static comparison.

Don’t

<DataTable />

Do

<Table aria-label="Plan comparison">
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Plan</Table.HeaderCell>
      <Table.HeaderCell>Price</Table.HeaderCell>
    </Table.Row>
  </Table.Head>
  <Table.Body>
    <Table.Row>
      <Table.Cell>Starter</Table.Cell>
      <Table.Cell>$0</Table.Cell>
    </Table.Row>
  </Table.Body>
</Table>

Installation

pnpm add @usefragments/ui

Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Additional packages

  • @tanstack/react-table — Optional — powers sorting, selection, and expansion; without it DataTable renders a static, non-interactive table.
pnpm add @tanstack/react-table

Anatomy

Import DataTable once, then compose its parts with dot notation.

  • DataTable.Columns
  • DataTable.preload

Examples

Loading

Placeholder rows, one bar per cell, sit under the real header so the page never jumps.

<DataTable
  columns={[
    { accessorKey: "name", header: "Name" },
    { accessorKey: "status", header: "Status" },
  ]}
  data={[]}

Rich Cells

A cell renderer drops any component — avatar, badge — straight into the column.

<DataTable
  columns={[
    {
      accessorKey: "name",
      header: "Name",
      cell: ({ row }) => (

Sortable

Click a header to sort; click it again to reverse.

<DataTable
  columns={[
    { accessorKey: "name", header: "Name" },
    { accessorKey: "amount", header: "Amount" },
  ]}
  data={[

Checkbox Selection

The header checkbox toggles every row at once.

<DataTable
  columns={[
    { accessorKey: "name", header: "Name" },
    { accessorKey: "status", header: "Status" },
  ]}
  data={[

Expandable Rows

Children fold away under their parent, like a file tree; depth shows by indent alone.

<DataTable
  columns={[
    { accessorKey: "name", header: "Name" },
    { accessorKey: "type", header: "Type" },
  ]}
  data={[

With Filters

Filter controls sit outside the table; you own the filtering.

<Stack gap="sm">
  <Input type="search" aria-label="Search users" placeholder="Search..." />
  <DataTable
    columns={[
      { accessorKey: "name", header: "Name" },
      { accessorKey: "status", header: "Status" },

Clickable Rows

Mouse or keyboard opens the row; getRowProps supplies its name.

<DataTable
  columns={[
    { accessorKey: "method", header: "Method" },
    { accessorKey: "path", header: "Path" },
  ]}
  data={[{ method: "GET", path: "/v1/components" }]}

Empty State

A compact EmptyState replaces the rows and keeps the headers in place.

<DataTable
  columns={[
    { accessorKey: "name", header: "Name" },
    { accessorKey: "status", header: "Status" },
  ]}
  data={[]}

Error

A failed load shows the error box under the real header, with one way out.

<DataTable
  columns={[
    { accessorKey: "name", header: "Name" },
    { accessorKey: "status", header: "Status" },
  ]}
  data={[]}

Long Cell Content

Oversized values scroll inside the table instead of stretching the page.

<DataTable
  columns={[
    { accessorKey: "name", header: "Administrator" },
    { accessorKey: "status", header: "Recovery policy" },
  ]}
  data={[

API reference

Component props
Prop
Type
Default
Description
borderedboolean | undefinedNot setDraw the sheet: the surface plane, a hairline border and the surface radius
captionstring | undefinedNot setVisible caption for the table (recommended for accessibility)
captionHiddenboolean | undefinedNot setHide the caption visually but keep it for screen readers
columnsRequiredDataTableColumn<T>[]Not setColumn definitions
dataRequiredT[]Not setData array
DataTableVirtual.borderedboolean | undefinedNot setDraw the sheet: the surface plane, a hairline border and the surface radius, framing the scrolling wrapper. Without it the table sits flush in the surface around it.
DataTableVirtual.captionReact.ReactNodeNot set—
DataTableVirtual.captionHiddenboolean | undefinedNot set—
DataTableVirtual.Cell.alignTableCellAlign | undefinedcenterstartendNot setAlign the cell's content; give the column's Table.HeaderCell the same.
DataTableVirtual.Cell.childrenReact.ReactNodeNot set—
DataTableVirtual.Cell.tabularNumsboolean | undefinedNot setUse tabular (fixed-width) numerals so digits align in columns. Ideal for numeric columns that update — counts, timestamps, currency.
DataTableVirtual.colSpanRequirednumberNot set—
DataTableVirtual.headerRequiredReact.ReactNodeNot set—
DataTableVirtual.HeaderCell.alignTableCellAlign | undefinedcenterstartendNot setAlign the header with its column: `end` over numbers.
DataTableVirtual.HeaderCell.childrenReact.ReactNodeNot set—
DataTableVirtual.HeaderCell.scopestring | undefinedNot setScope for the header cell
DataTableVirtual.maxHeightstring | number | undefinedNot setBound the table's height. The wrapper then scrolls on both axes and the head sticks to its top; without it the page scrolls the rows and the head scrolls with them.
DataTableVirtual.paddingBottomRequirednumberNot set—
DataTableVirtual.paddingTopRequirednumberNot set—
DataTableVirtual.renderRowRequired(row: VirtualTableRow<TItem>) => React.ReactNodeNot set—
DataTableVirtual.Row.childrenReact.ReactNodeNot set—
DataTableVirtual.Row.selectedboolean | undefinedNot setMark the row as chosen: the selection wash plus its 1px inset ring.
DataTableVirtual.virtualRowsRequiredVirtualTableRow<TItem>[]Not set—
DataTableVirtual.wrapperClassNamestring | undefinedNot setClass applied to the wrapper element that scrolls a wide table
DataTableVirtual.wrapperPropsReact.HTMLAttributes<HTMLDivElement> | undefinedNot setProps applied to the wrapper element that scrolls a wide table
emptyStateReact.ReactNodeNot setWhat an empty result says: EmptyState parts (`EmptyState.Title`, `EmptyState.Description`, `EmptyState.Actions`) rendered in a compact EmptyState under the real header. Defaults to a one-line "No data".
errorReact.ReactNodeNot setA failed load: what went wrong and what to do, shown in an error box under the real header. Ignored while `loading`.
expandedExpandedState | undefinedNot setControlled expanded state
getRowId((row: T) => string) | undefinedNot setUnique key extractor for each row
getRowProps((row: T) => React.HTMLAttributes<HTMLTableRowElement>) | undefinedNot setProps applied to each rendered data row. Use this for row-level ARIA labels, roles, and data attributes.
getSubRows((row: T) => T[] | undefined) | undefinedNot setExtract sub-rows from a row for expandable tree tables
hideHeaderboolean | undefinedNot setHide the column header row (e.g. stacked per-group tables share one).
loadingboolean | undefinedNot setWhen true, render placeholder rows (one bar per cell) instead of data.
maxHeightstring | number | undefinedNot setBound the table's height: the rows scroll inside it and the head sticks
onExpandedChangeOnChangeFn<ExpandedState> | undefinedNot setExpanded state change handler
onRetry(() => void) | undefinedNot setRetry the failed load. Shows a retry button in the error box.
onRowClick((row: T, event: DataTableRowClickEvent) => void) | undefinedNot setRow click handler
onRowSelectionChangeOnChangeFn<RowSelectionState> | undefinedNot setSelection change handler
onSortingChangeOnChangeFn<SortingState> | undefinedNot setSorting change handler
retryLabelstring | undefined"Retry"Label for the retry button.
rowSelectionRowSelectionState | undefinedNot setControlled selection state
selectableboolean | undefinedNot setEnable row selection
showCheckboxboolean | undefinedNot setShow checkbox column for row selection
skeletonRowsnumber | undefinedNot setNumber of placeholder rows to show while loading (default 6).
sortableboolean | undefinedNot setEnable sorting
sortingSortingState | undefinedNot setControlled sorting state
wrapperClassNamestring | undefinedNot setAdditional class name for the outer wrapper div
wrapperPropsReact.HTMLAttributes<HTMLDivElement> | undefinedNot setProps forwarded to the outer wrapper div

Accessibility

  • Headers stay real th elements with scope
  • Sortable headers are buttons — reachable by keyboard
  • Selection checkboxes carry their own labels
  • Expand toggles report aria-expanded
  • TablealternativeUse Table for simple semantic HTML tables
  • EmptyStatesiblingUse EmptyState for empty table states
  • BadgesiblingUse Badge for status columns
  • MenusiblingUse Menu for filter dropdowns
  • CheckboxsiblingBuilt-in checkbox selection via showCheckbox

Next steps