1
ComponentsData Display

Data Table

A sortable project table with typed columns, active rows, row actions and empty states.

Purpose

DataTable adds typed columns, sorting, active-row behavior, and empty states to the Siteplane UI Table primitive. Use Table directly for static markup.

Sortable invoices

Acme Co.Paid$2,400
Linear LabsDraft$860
NorthwindOpen$1,280
"use client";

import { useState } from "react";
import { DataTable, type DataTableColumn, type DataTableSort } from "@/components/ui/data-table";

export function Example() {
  const [sort, setSort] = useState<DataTableSort | null>({ id: "customer", direction: "asc" });
  return <DataTable columns={columns} data={rows} sort={sort} onSortChange={setSort} />;
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/data-table

Import

import { DataTable } from "@/components/ui/data-table";
import type { DataTableColumn } from "@/components/ui/data-table";

Need the machine-readable source metadata? View registry JSON.

Usage

Use Data Table when a table needs managed sorting. Use Table directly for static tabular data.

"use client";

import { useState } from "react";
import { DataTable, type DataTableColumn, type DataTableSort } from "@/components/ui/data-table";

export function Example() {
  const [sort, setSort] = useState<DataTableSort | null>({ id: "customer", direction: "asc" });
  return <DataTable columns={columns} data={rows} sort={sort} onSortChange={setSort} />;
}

Examples

Sorting modes, row interaction, empty states, caption, footer and the card variant are props of the same component.

Sorting disabled

Columns with sortable: false keep a plain header without a sort button.

Team
Website RedesignFrontend$12,500
Mobile AppMobile$8,750
API IntegrationBackend$5,200
Database MigrationDevOps$3,800
const columns: DataTableColumn<ProjectRow>[] = [
  { id: "project", accessor: (row) => row.project, header: "Project" },
  { id: "team", accessor: (row) => row.team, header: "Team", sortable: false },
  { id: "budget", accessor: (row) => row.budget, align: "right", header: "Budget" },
];

<DataTable columns={columns} data={projectRows} getRowId={(row) => row.id} />

Initial sort

initialSort sets the uncontrolled start state; header clicks take over from there.

Website RedesignFrontend$12,500
Mobile AppMobile$8,750
API IntegrationBackend$5,200
Database MigrationDevOps$3,800
<DataTable
  columns={projectColumns}
  data={projectRows}
  getRowId={(row) => row.id}
  initialSort={{ direction: "desc", id: "budget" }}
/>

Full-cell sort area

sortClickArea="cell" makes the whole header cell clickable instead of only label and icon.

Website RedesignFrontend$12,500
Mobile AppMobile$8,750
API IntegrationBackend$5,200
Database MigrationDevOps$3,800
<DataTable
  columns={projectColumns}
  data={projectRows}
  getRowId={(row) => row.id}
  sortClickArea="cell"
/>

Clickable rows

onRowClick makes rows keyboard-focusable; activeRowId highlights the current row.

Website RedesignFrontend$12,500
Mobile AppMobile$8,750
API IntegrationBackend$5,200
Database MigrationDevOps$3,800
const [activeRowId, setActiveRowId] = useState("website-redesign");

<DataTable
  activeRowId={activeRowId}
  columns={projectColumns}
  data={projectRows}
  getRowAriaLabel={(row) => `Set ${row.project} active`}
  getRowId={(row) => row.id}
  onRowClick={(row, rowId) => setActiveRowId(rowId)}
/>

Empty state

No matching projects
Clear filters or create a project to populate this table.
<DataTable
  columns={projectColumns}
  data={[]}
  emptyState={
    <Empty className="min-h-48 py-10 md:py-12">
      <EmptyHeader>
        <EmptyTitle>No matching projects</EmptyTitle>
        <EmptyDescription>Clear filters or create a project to populate this table.</EmptyDescription>
      </EmptyHeader>
      <EmptyContent>
        <Button variant="outline">Clear filters</Button>
      </EmptyContent>
    </Empty>
  }
/>

Card variant

Website RedesignFrontend$12,500
Mobile AppMobile$8,750
API IntegrationBackend$5,200
Database MigrationDevOps$3,800
<DataTable
  columns={projectColumns}
  data={projectRows}
  getRowId={(row) => row.id}
  variant="card"
/>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Accessor columns are sortable by default. Set sortable: false when sorting has no product meaning.
  • Use sort with onSortChange for controlled sorting or initialSort for uncontrolled initial state.
  • column.align accepts "left", "center", or "right".
  • Without a custom cell, Date values use toLocaleDateString(), nullish values render empty, and other values use String(value).
  • When emptyState is omitted, DataTable renders its built-in “No results” state.
  • onRowClick, getRowId, activeRowId, and getRowAriaLabel create an accessible active-row flow.
  • Row clicks ignore links, buttons, inputs, selects, textareas, [role=button], [role=link], and elements marked data-row-click-ignore="true".
  • sortClickArea="cell" makes the complete sortable header cell interactive; the default is "content".

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • DataTable adds no mount, exit, or sorting animation.
  • Sort-header Buttons disable scale so header text remains stationary.
  • Row hover and active styling come from Table.

Accessibility

  • Sortable headers expose aria-sort; their icons are decorative.
  • Clickable rows require a stable accessible label and support Enter and Space.
  • Interactive cell controls remain independent of the row action and must keep visible focus.
  • Active row state is contextual selection, not a substitute for checkbox-based bulk selection.
  • Empty states need a short explanation and, when possible, a useful next action.

Implementation Guidance

  • Keep Table as the native rendering layer and DataTable as the small typed behavior layer; do not add a TanStack dependency.
  • Define behavior in DataTableColumn rather than duplicating sorting in feature components.
  • Use row clicks only for opening or activating row context.

On This Page