Data table
A table described by column definitions. Sorting, selection, expandable rows and a layout that adapts to its container.
import { DataTable } from "@openperf/deft";
import { Preview } from "../../src/Preview";
import { COLUMNS } from "./columns";
import { ROWS } from "./rows";
export default function DataTableBasic() {
return (
<Preview block={true}>
<DataTable columns={COLUMNS} data={ROWS} />
</Preview>
);
}
- Use it for a list of records with the same columns. Use the Table parts directly for grouped rows or rows that differ in shape.
- Columns are TanStack Table column definitions;
metasets how a column looks and how it adapts to narrow containers. - Give every column a fixed
widthexcept the one that takes the remaining space, usually thetitlecolumn, so header and rows line up. - Pass
datain display order. The table never sorts or filters it. - Rows are keyed by
getRowId, or by a stringidon the row.getRowIdis required when rows have no stringid.
Sorting, selection and links
import { DataTable, type DataTableSorting } from "@openperf/deft";
import { useState } from "react";
import { Preview } from "../../src/Preview";
import { COLUMNS } from "./columns";
import { ROWS } from "./rows";
export default function DataTableSortingSelectionAndLinks() {
const [sorting, setSorting] = useState<DataTableSorting["state"]>([{ id: "value", desc: true }]);
const [selected, setSelected] = useState<ReadonlySet<string>>(new Set());
return (
<Preview block={true}>
<DataTable
columns={COLUMNS}
data={sorting[0]?.desc ? ROWS.toReversed() : ROWS}
link={(row) => ({ href: `#${row.id}` })}
selection={{ selected, onChange: setSelected }}
sorting={{ state: sorting, onChange: setSorting }}
/>
</Preview>
);
}
sorting makes the columns that have an accessorFn sortable. The first click sorts descending, the next ascending. sorting.onChange receives the next SortingState and the caller loads the data in that order.
selection adds a checkbox column with select-all; Shift-click selects a range. link turns the title column into the row’s destination; onRowClick is for a row action without one.
Expandable rows
import { DataTable } from "@openperf/deft";
import { Preview } from "../../src/Preview";
import { COLUMNS } from "./columns";
import { ROWS } from "./rows";
export default function DataTableExpandable() {
return (
<Preview block={true}>
<DataTable columns={COLUMNS} data={ROWS} renderDetail={(row) => `Details of ${row.name}`} />
</Preview>
);
}
renderDetail adds a chevron column and renders its result under an open row. A click anywhere on the row opens it, and onRowClick is ignored. Cells read row.getIsExpanded() to change their content when the row is open.
Loading and empty
import { DataTable } from "@openperf/deft";
import { Preview } from "../../src/Preview";
import { COLUMNS } from "./columns";
export default function DataTableStates() {
return (
<Preview block={true}>
<DataTable columns={COLUMNS} data={[]} loadingRows={3} pending={true} />
<DataTable
columns={COLUMNS}
data={[]}
empty={{ title: "Title", description: "Description" }}
/>
</Preview>
);
}
pending replaces the body with loadingRows skeleton rows. After a successful load with no rows, empty fills the body; omit it while the request is failing.
Adaptive layout
The table reacts to the width of its container, not the viewport.
- In the compact layout, columns with
priority: "low"are hidden andcompactWidthreplaceswidth. - In the stacked layout, the narrowest, the header is hidden and each row becomes a two-line card. The
titleandasidecells share the first line,metacells wrap onto a smaller second line, andleadcells (checkbox, chevron, status icon) sit in a gutter on the left. Number cells on the second line are prefixed with their header.priority: "medium"hides visual-only columns, such as meters, in this layout.
Column kinds
| Kind | Area | Defaults |
|---|---|---|
title |
title |
Medium weight; wrapped by link. |
number |
meta |
End-aligned tabular figures, secondary tone. |
date |
aside |
End-aligned tabular figures, icon tone. |
actions |
aside |
End-aligned. |
text |
meta |
The default kind. |
Set area, align, tone or weight in meta to override a kind’s default, for example to move a key metric to aside.
API
columnsDataTableColumnDef<TData>[]
DataTableColumnDef<TData>[]datareadonly TData[]
Rows in display order.
readonly TData[]getRowId?(row: TData) => string
Required when rows have no string id.
(row: TData) => stringcolumnVisibility?Record<string, boolean>
Hides columns by id regardless of width.
Record<string, boolean>sorting?{ state: SortingState; onChange: (next: SortingState) => void }
Manual sorting.
{ state: SortingState; onChange: (next: SortingState) => void }selection?{ selected: ReadonlySet<string>; onChange: (next: Set<string>) => void }
Row selection by row id.
{ selected: ReadonlySet<string>; onChange: (next: Set<string>) => void }link?(row: TData) => TableRowLinkProps | undefined
Row destination: href, or render for a router link.
(row: TData) => TableRowLinkProps | undefinedonRowClick?(row: TData) => void
Row action without a link. Ignored with renderDetail.
(row: TData) => voidrenderDetail?(row: TData) => ReactNode
Expandable row panel.
(row: TData) => ReactNodepending?boolean
Shows skeleton rows.
booleanloadingRows?number
Skeleton row count.
number8empty?{ title: ReactNode; description?: ReactNode; actions?: ReactNode }
Empty state.
{ title: ReactNode; description?: ReactNode; actions?: ReactNode }hideHeader?boolean
Omits the header row.
booleanaria-label?string
Names the table when no heading does.
stringdensity?"default" | "compact" | "dense"
Row height.
"default" | "compact" | "dense""default"Column meta
kind?"title" | "number" | "date" | "actions" | "text"
"title" | "number" | "date" | "actions" | "text""text"area?"lead" | "title" | "aside" | "meta"
Place in the stacked layout.
"lead" | "title" | "aside" | "meta"width?number | string
A number is in pixels, a string is a grid track. Omitted, the column takes the remaining space.
number | stringcompactWidth?number | string
Replaces width in the compact layout.
number | stringpriority?"high" | "medium" | "low"
Which layouts hide the column.
"high" | "medium" | "low"align?"start" | "end"
"start" | "end"tone?"default" | "secondary" | "muted" | "icon"
"default" | "secondary" | "muted" | "icon"weight?"regular" | "medium"
"regular" | "medium"truncate?boolean
Cuts long content with an ellipsis.
booleanBuilt on Table, Checkbox and Empty, with TanStack Table for the row model.