Sortable data table
Compare structured records with sortable columns.
datatable
Preview
Sortable data table preview
Live · keyboard ready
| Manicat UI | Elia | Active | 24 | |
| Halo | Denis | Active | 12 | |
| Quartz | Sofia | Active | 9 | |
| Atlas | Mara | Trialing | 6 | |
| Nimbus | Ravi | Paused | 3 |
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
Live · keyboard ready
| Umbrella | Paid | 12400 | |
| Acme Corp | Paid | 4200 | |
| Hooli | Pending | 3300 | |
| Globex | Pending | 1850 | |
| Initech | Overdue | 960 |
invoices-table-sortable-columns-a-default-sort-on-the-amount-and-row-selection-for-bulk-actions-.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 Invoice = { id: string; customer: string; status: "Paid" | "Pending" | "Overdue"; amount: number; [key: string]: unknown };
const invoices: Invoice[] = [
{ id: "inv-1", customer: "Acme Corp", status: "Paid", amount: 4200 },
{ id: "inv-2", customer: "Globex", status: "Pending", amount: 1850 },
{ id: "inv-3", customer: "Initech", status: "Overdue", amount: 960 },
{ id: "inv-4", customer: "Umbrella", status: "Paid", amount: 12400 },
{ id: "inv-5", customer: "Hooli", status: "Pending", amount: 3300 },
];
const invoiceColumns: DataColumn<Invoice>[] = [
{ key: "customer", label: "Customer", sortable: true },
{ key: "status", label: "Status", sortable: true },
{ key: "amount", label: "Amount (USD)", sortable: true, numeric: true },
];
export function SortableDataTableInvoicesExample() {
return (
<SortableDataTable
rows={invoices}
columns={invoiceColumns}
rowKey="id"
caption="Invoices"
defaultSort={{ key: "amount", direction: "desc" }}
selectable
itemName={{ one: "invoice", other: "invoices" }}
/>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| ArrowLeft / ArrowRight | Moves between sortable headers. |
| ArrowUp / ArrowDown | Moves between row checkboxes; down from Select all enters the rows. |
| Home / End | Jumps to the first or last header or row checkbox. |
| Enter / Space | Sorts by a header or toggles a checkbox. |
| Shift + click | Selects a range of rows. |
| Escape | Clears 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.