Skip to content
OpenPerf
Esc
↑↓navigate↵open⌘Jpreview
On this page

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; meta sets how a column looks and how it adapts to narrow containers.
  • Give every column a fixed width except the one that takes the remaining space, usually the title column, so header and rows line up.
  • Pass data in display order. The table never sorts or filters it.
  • Rows are keyed by getRowId, or by a string id on the row. getRowId is required when rows have no string id.
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 and compactWidth replaces width.
  • In the stacked layout, the narrowest, the header is hidden and each row becomes a two-line card. The title and aside cells share the first line, meta cells wrap onto a smaller second line, and lead cells (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

PropType
columnsDataTableColumnDef<TData>[]
TypeDataTableColumnDef<TData>[]
datareadonly TData[]

Rows in display order.

Typereadonly TData[]
getRowId?(row: TData) => string

Required when rows have no string id.

Type(row: TData) => string
columnVisibility?Record<string, boolean>

Hides columns by id regardless of width.

TypeRecord<string, boolean>
sorting?{ state: SortingState; onChange: (next: SortingState) => void }

Manual sorting.

Type{ state: SortingState; onChange: (next: SortingState) => void }
selection?{ selected: ReadonlySet<string>; onChange: (next: Set<string>) => void }

Row selection by row id.

Type{ selected: ReadonlySet<string>; onChange: (next: Set<string>) => void }
link?(row: TData) => TableRowLinkProps | undefined

Row destination: href, or render for a router link.

Type(row: TData) => TableRowLinkProps | undefined
onRowClick?(row: TData) => void

Row action without a link. Ignored with renderDetail.

Type(row: TData) => void
renderDetail?(row: TData) => ReactNode

Expandable row panel.

Type(row: TData) => ReactNode
pending?boolean

Shows skeleton rows.

Typeboolean
loadingRows?number

Skeleton row count.

Typenumber
Default8
empty?{ title: ReactNode; description?: ReactNode; actions?: ReactNode }

Empty state.

Type{ title: ReactNode; description?: ReactNode; actions?: ReactNode }
hideHeader?boolean

Omits the header row.

Typeboolean
aria-label?string

Names the table when no heading does.

Typestring
density?"default" | "compact" | "dense"

Row height.

Type"default" | "compact" | "dense"
Default"default"

Column meta

PropType
kind?"title" | "number" | "date" | "actions" | "text"
Type"title" | "number" | "date" | "actions" | "text"
Default"text"
area?"lead" | "title" | "aside" | "meta"

Place in the stacked layout.

Type"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.

Typenumber | string
compactWidth?number | string

Replaces width in the compact layout.

Typenumber | string
priority?"high" | "medium" | "low"

Which layouts hide the column.

Type"high" | "medium" | "low"
align?"start" | "end"
Type"start" | "end"
tone?"default" | "secondary" | "muted" | "icon"
Type"default" | "secondary" | "muted" | "icon"
weight?"regular" | "medium"
Type"regular" | "medium"
truncate?boolean

Cuts long content with an ellipsis.

Typeboolean

Built on Table, Checkbox and Empty, with TanStack Table for the row model.

Was this page helpful?