Data Table
A slot-based table with sorting, drag-to-group, and column reorder or show/hide.
| Stack: .NET(2) | |||
| Legacy | .NET | Enterprise | Deprecated |
| Hub | .NET | Enterprise | Deprecated |
| Stack: Next.js(3) | |||
| Docs | Next.js | Platform | Active |
| Insights | Next.js | Data | Active |
| Pulse | Next.js | Data | Active |
| Stack: React(3) | |||
| Numa | React | Platform | Active |
| Atlas | React | Platform | Active |
| Flow | React | Platform | Active |
When to use
Reach for this when a Platform screen needs a working grid: click-to-sort, drag-to-group with collapsible sections and aggregates, column reorder, show/hide columns, and that arrangement remembered per user. If none of those apply, the plain Table is the right answer. If you need row virtualisation, server-driven pagination or row selection, build on TanStack Table directly instead of stretching this component.
Platform notes
The package carries no shadcn, i18n or icon dependency: you supply slots (Table, Button, DropdownMenu), icons and texts, so the wired wrapper lives in shared/ui and texts is fed from useTranslation, including the function entries (stopGrouping, columnsCount). Sorting and grouping are computed client-side over the rows array you pass, so under server-side paging it only orders the current page: sort on the server and hand it defaultSort to match. Column order, visibility and grouping persist in localStorage under dt:<tableId>:*, which makes tableId a stable contract (reusing one across two grids crosses their state, and renaming a column key orphans the saved entry). On keyboard, header sorting is a real button, but column reorder and adding a group are HTML5 drag only and group rows toggle on row click, so give keyboard users another route to those if the grid is a primary workflow.
Install
pnpm add @plainconceptsplatform/ui-componentsImport from the sub-path: @plainconceptsplatform/ui-components/data-table.
Code
"use client";
import { Badge } from "@/components/ui/badge";
import { DataTable } from "@/components/ui/data-table";
import type { DataTableColumn } from "@plainconceptsplatform/ui-components/data-table";
type App = {
id: string;
name: string;
stack: string;
status: "Active" | "Deprecated";
team: string;
};
const apps: App[] = [
{ id: "1", name: "Numa", stack: "React", status: "Active", team: "Platform" },
{ id: "2", name: "Docs", stack: "Next.js", status: "Active", team: "Platform" },
{ id: "3", name: "Legacy", stack: ".NET", status: "Deprecated", team: "Enterprise" },
{ id: "4", name: "Insights", stack: "Next.js", status: "Active", team: "Data" },
{ id: "5", name: "Atlas", stack: "React", status: "Active", team: "Platform" },
{ id: "6", name: "Pulse", stack: "Next.js", status: "Active", team: "Data" },
{ id: "7", name: "Hub", stack: ".NET", status: "Deprecated", team: "Enterprise" },
{ id: "8", name: "Flow", stack: "React", status: "Active", team: "Platform" },
];
const columns: DataTableColumn<App>[] = [
{
key: "name",
label: "App",
render: (r) => <span className="font-medium">{r.name}</span>,
sortValue: (r) => r.name,
groupValue: (r) => r.stack,
},
{
key: "stack",
label: "Stack",
render: (r) => r.stack,
sortValue: (r) => r.stack,
},
{
key: "team",
label: "Team",
render: (r) => r.team,
sortValue: (r) => r.team,
},
{
key: "status",
label: "Status",
render: (r) => (
<Badge variant={r.status === "Active" ? "default" : "secondary"}>{r.status}</Badge>
),
sortValue: (r) => r.status,
},
];
export function DataTableDemo() {
return (
<div className="w-full">
<DataTable
tableId="docs-data-table-demo"
rows={apps}
rowKey={(r) => r.id}
columns={columns}
defaultGroupBy={["stack"]}
/>
</div>
);
}