Sortable data table

Compare structured records with sortable columns.

datatable
Preview
Sortable data table preview
Projects in the workspace
Manicat UIEliaActive24
HaloDenisActive12
QuartzSofiaActive9
AtlasMaraTrialing6
NimbusRaviPaused3

When to use

  • Tabular records people sort and select, such as projects, invoices, or users.
  • Tables that need Shift-click range selection and a count line with Clear.

When not to use

  • Use data-grid when people edit cells like a spreadsheet.
  • Use timeline for chronological activity.
  • Use reorderable-list when people set the order by hand.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sortable-data-table",
  "type": "registry:ui",
  "title": "Sortable data table",
  "description": "Compare structured records with sortable columns.",
  "categories": [
    "data"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/sortable-data-table/sortable-data-table.tsx",
      "type": "registry:component",
      "target": "@components/manicat/sortable-data-table/sortable-data-table.tsx"
    },
    {
      "path": "registry/components/sortable-data-table/sortable-data-table.module.css",
      "type": "registry:component",
      "target": "@components/manicat/sortable-data-table/sortable-data-table.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/sortable-data-table",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/sortable-data-table",
    "markdown": "https://uiarc.dev/components/sortable-data-table/markdown",
    "tags": [
      "data",
      "table"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { SortableDataTable } from "@/components/manicat/sortable-data-table/sortable-data-table";
import type { DataColumn } from "@/components/manicat/sortable-data-table/sortable-data-table";

type Project = { id: string; name: string; owner: string; status: "Active" | "Trialing" | "Paused"; seats: number; [key: string]: unknown };

const projects: Project[] = [
  { id: "p1", name: "Manicat UI", owner: "Elia", status: "Active", seats: 24 },
  { id: "p2", name: "Atlas", owner: "Mara", status: "Trialing", seats: 6 },
  { id: "p3", name: "Halo", owner: "Denis", status: "Active", seats: 12 },
  { id: "p4", name: "Nimbus", owner: "Ravi", status: "Paused", seats: 3 },
  { id: "p5", name: "Quartz", owner: "Sofia", status: "Active", seats: 9 },
];

const projectColumns: DataColumn<Project>[] = [
  { key: "name", label: "Project", sortable: true },
  { key: "owner", label: "Owner", sortable: true },
  { key: "status", label: "Status", sortable: true },
  { key: "seats", label: "Seats", sortable: true, numeric: true },
];

export function SortableDataTableExample() {
  return (
    <SortableDataTable
      rows={projects}
      columns={projectColumns}
      rowKey="id"
      caption="Projects in the workspace"
      defaultSort={{ key: "seats", direction: "desc" }}
      selectable
      itemName={{ one: "project", other: "projects" }}
    />
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Invoices table Sortable columns, a default sort on the amount and row selection for bulk actions.

Invoices table Sortable columns, a default sort on the amount and row selection for bulk actions. preview
Invoices
UmbrellaPaid12400
Acme CorpPaid4200
HooliPending3300
GlobexPending1850
InitechOverdue960

Keyboard interactions

KeysAction
ArrowLeft / ArrowRightMoves between sortable headers.
ArrowUp / ArrowDownMoves between row checkboxes; down from Select all enters the rows.
Home / EndJumps to the first or last header or row checkbox.
Enter / SpaceSorts by a header or toggles a checkbox.
Shift + clickSelects a range of rows.
EscapeClears the selection.

Accessibility

  • A native table with caption, scoped headers, and aria-sort on sortable columns.
  • Sort buttons are labelled like "Sort by Budget, currently ascending"; the select all checkbox shows a mixed state.
  • Sort and selection changes are announced through a role="status" region.

Motion

  • Rows glide to their new positions on a spring when the sort changes; the sort arrow flips.
  • The selection count rolls and Clear fades in with a short blur.
  • Reduced motion reorders instantly and fades the count.

Responsive behavior

  • Wider layouts scroll horizontally inside the table instead of the page.
  • Below 620px each row folds into two lines, and the header becomes a scrolling strip of sort buttons with Select all pinned.
  • Hover row fills apply only on hover-capable fine pointers.

Performance

  • Rows are not virtualized and each uses position layout animation for re-sorts; paginate long lists with pagination.
  • Sorting runs client side over the rows you pass.

Notes for AI

  • Use for tabular records people sort and select. Use timeline for chronological activity and a plain list for simple items.
  • Sorting is client side over the given rows; for server sorting, control defaultSort per fetch and page with pagination.
  • Use render for badges, avatars, or formatted numbers inside cells.