Components / Data display

SortableDataTable

Compare structured records with sortable columns.

Install

npx shadcn@latest add https://irisui.dev/r/sortable-data-table.json
import { SortableDataTable } from "@/components/iris/sortable-data-table/sortable-data-table";
Registry item sortable-data-tableBrings in foundationRegistry JSON

Preview

SortableDataTable

A data table with click-to-sort columns, right-aligned numeric columns, optional row selection and rows that glide to their new order.

When to use

  • Comparing structured records side by side: projects, listings, contacts.
  • When people need to reorder by a column or select several rows for a bulk action.
  • Use FilterToolbar above it for filters, Pagination below it for long sets, and Timeline when order is chronological and not user-sortable.

Usage

import { SortableDataTable } from "@/components/iris/sortable-data-table/sortable-data-table";

h(SortableDataTable, {
  caption: "Projects",
  rows, // [{ id, name, plan, state, storage }]
  rowKey: "id",
  defaultSort: { key: "storage", direction: "desc" },
  selectable: true,
  itemName: { one: "project", other: "projects" },
  columns: [
    { key: "name", label: "Project" },
    { key: "state", label: "State", render: (v) => h(Badge, { size: "sm" }, v) },
    { key: "storage", label: "Storage", numeric: true, render: (v) => `${v.toFixed(1)} GiB` },
  ],
});

Props

Prop Type Default Description
rows T[] — (required) Records (plain objects).
columns DataColumn<T>[] — (required) { key; label; sortable?; numeric?; width?; render?(value, row) }. Columns sort unless sortable: false. numeric right-aligns with tabular numerals (auto-detected when every value is a number). Without render, values show as text and objects as JSON.
rowKey keyof T | (row) => string — (required) Stable key per row.
caption string "Data table" Accessible table caption (visually hidden).
emptyMessage string "No rows to show" Shown when rows is empty.
defaultSort / onSortChange { key; direction: "asc" | "desc" } / (sort) => void — Initial sort and change callback. Clicking a header toggles asc/desc.
selectable boolean false Adds a checkbox column, row-click selection and a count line with "Clear selection".
selectedKeys / defaultSelectedKeys / onSelectionChange string[] / string[] / (keys) => void — Controlled or uncontrolled selection.
itemName { one: string; other: string } { one: "row", other: "rows" } Noun for the count line.

Notes

  • Headers are buttons with aria-sort; the new sort is announced. Arrow keys move between header controls and row checkboxes, Shift-click selects a range, Escape clears the selection.
  • Columns are measured once and held, so sorting never reflows widths; set width to fix a column.
  • Rows animate to their new position on sort; instant under reduced motion.
  • Below 620px the header becomes a scrolling strip of sort controls and each row folds into two lines: the first column with the first numeric column beside it, then the rest.