Data Table

Typed, zero-dependency data grid that auto-switches between a hand-rolled virtualized engine (10 to 100,000 rows) and a standard engine with row grouping and rowspan. Search, single and multi sort, multi-select, row expansion, frozen columns, column groups, pinned rows, and feature presets.

UniqueUI CLI

npx uniqueui add data-table

shadcn CLI

npx shadcn@latest add https://uniqueui-platform.vercel.app/r/data-table.json -y

shadcn path expects @/lib/utils (run shadcn init first). Same source file is installed to components/ui/.

Basic preset

Variant 1 of 19
Alex KimEngineerPlatformActive2023-01-09
Sara ChenDesignerProductActive2023-03-14
Jordan LeePMGrowthAway2023-06-02
Maya PatelEngineerPlatformActive2024-01-22
Ryan WuDesignerProductAway2024-02-11
Priya ShahPMGrowthActive2024-04-30
"use client";
import { DataTable, type DataTableColumn } from "@/components/ui/data-table";

interface Member {
  id: string;
  name: string;
  role: string;
  department: string;
  joined: Date;
}

const data: Member[] = [
  { id: "1", name: "Alex Kim", role: "Engineer", department: "Platform", joined: new Date("2023-01-09") },
  { id: "2", name: "Sara Chen", role: "Designer", department: "Product", joined: new Date("2023-03-14") },
  { id: "3", name: "Jordan Lee", role: "PM", department: "Growth", joined: new Date("2023-06-02") },
  { id: "4", name: "Maya Patel", role: "Engineer", department: "Platform", joined: new Date("2024-01-22") },
  { id: "5", name: "Ryan Wu", role: "Designer", department: "Product", joined: new Date("2024-02-11") },
  { id: "6", name: "Priya Shah", role: "PM", department: "Growth", joined: new Date("2024-04-30") },
  { id: "7", name: "Sam Rivera", role: "Engineer", department: "Platform", joined: new Date("2024-05-18") },
];

const columns: DataTableColumn<Member>[] = [
  { id: "name", header: "Name", accessor: (row) => row.name },
  { id: "role", header: "Role", accessor: (row) => row.role },
  { id: "department", header: "Department", accessor: (row) => row.department },
  { id: "joined", header: "Joined", accessor: (row) => row.joined, cell: (row) => row.joined.toISOString().slice(0, 10) },
];

export default function Example() {
  return (
    <div className="w-full p-6">
      <DataTable data={data} columns={columns} getRowId={(row) => row.id} preset="basic" pageSize={5} theme="dark" />
    </div>
  );
}

Advanced preset

Variant 2 of 19
#2400Alex Kim$24.00Paid2026-01-01
#2401Sara Chen$77.70Pending2026-02-02
#2402Jordan Lee$131.40Refunded2026-03-03
#2403Maya Patel$185.10Paid2026-04-04
#2404Ryan Wu$238.80Pending2026-05-05
#2405Priya Shah$292.50Refunded2026-06-06
#2406Alex Kim$346.20Paid2026-07-07
#2407Sara Chen$399.90Pending2026-08-08
"use client";
import { DataTable, type DataTableColumn } from "@/components/ui/data-table";

interface Order {
  id: string;
  order: string;
  customer: string;
  total: number;
  status: string;
}

const data: Order[] = Array.from({ length: 24 }, (_, i) => ({
  id: `ord-${i + 1}`,
  order: `#${String(2400 + i)}`,
  customer: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
  total: Math.round((i * 53.7 + 24) * 100) / 100,
  status: ["Paid", "Pending", "Refunded"][i % 3],
}));

const columns: DataTableColumn<Order>[] = [
  { id: "order", header: "Order", accessor: (row) => row.order },
  { id: "customer", header: "Customer", accessor: (row) => row.customer },
  { id: "total", header: "Total", accessor: (row) => row.total, cell: (row) => `$${row.total.toFixed(2)}`, align: "right" },
  { id: "status", header: "Status", accessor: (row) => row.status },
];

export default function Example() {
  return (
    <div className="w-full p-6">
      <DataTable
        data={data}
        columns={columns}
        getRowId={(row) => row.id}
        preset="advanced"
        pageSize={8}
        border
        renderExpanded={(row) => (
          <div className="text-xs opacity-80">
            Order {row.order} for {row.customer} — status {row.status}.
          </div>
        )}
        theme="dark"
      />
    </div>
  );
}

Virtualized 10k rows

Variant 3 of 19
Event 1api12error
Event 2web49ok
Event 3worker86ok
Event 4cron123ok
Event 5api160ok
Event 6web197ok
Event 7worker234ok
Event 8cron271ok
Event 9api308ok
Event 10web345ok
Event 11worker382ok
Event 12cron419ok
Event 13api456ok
Event 14web493error
Event 15worker530ok
"use client";
import { useMemo } from "react";
import { DataTable, type DataTableColumn } from "@/components/ui/data-table";

interface EventRow {
  id: string;
  event: string;
  service: string;
  latency: number;
  status: string;
}

const columns: DataTableColumn<EventRow>[] = [
  { id: "event", header: "Event", accessor: (row) => row.event },
  { id: "service", header: "Service", accessor: (row) => row.service },
  { id: "latency", header: "Latency (ms)", accessor: (row) => row.latency, align: "right" },
  { id: "status", header: "Status", accessor: (row) => row.status },
];

export default function Example() {
  const data = useMemo<EventRow[]>(
    () =>
      Array.from({ length: 10000 }, (_, i) => ({
        id: `evt-${i}`,
        event: `Event ${i + 1}`,
        service: ["api", "web", "worker", "cron"][i % 4],
        latency: ((i * 37) % 900) + 12,
        status: i % 13 === 0 ? "error" : "ok",
      })),
    []
  );
  return (
    <div className="w-full p-6">
      <DataTable
        data={data}
        columns={columns}
        getRowId={(row) => row.id}
        preset="enterprise"
        maxHeight={400}
        rowHeight={44}
        theme="dark"
      />
    </div>
  );
}

Grouped rows + rowspan

Variant 4 of 19
TeamNameRegionOn call
PlatformAlex KimWestYes
PlatformMaya PatelNo
PlatformSam RiveraEastNo
ProductSara ChenEastYes
ProductRyan WuCentralNo
GrowthJordan LeeCentralNo
GrowthPriya ShahWestYes
"use client";
import { DataTable, type DataTableColumn } from "@/components/ui/data-table";

interface Person {
  id: string;
  name: string;
  team: string;
  region: string;
  oncall: string;
}

const data: Person[] = [
  { id: "1", name: "Alex Kim", team: "Platform", region: "West", oncall: "Yes" },
  { id: "2", name: "Maya Patel", team: "Platform", region: "West", oncall: "No" },
  { id: "3", name: "Sam Rivera", team: "Platform", region: "East", oncall: "No" },
  { id: "4", name: "Sara Chen", team: "Product", region: "East", oncall: "Yes" },
  { id: "5", name: "Ryan Wu", team: "Product", region: "Central", oncall: "No" },
  { id: "6", name: "Jordan Lee", team: "Growth", region: "Central", oncall: "No" },
];

const columns: DataTableColumn<Person>[] = [
  { id: "team", header: "Team", accessor: (row) => row.team },
  { id: "name", header: "Name", accessor: (row) => row.name },
  { id: "region", header: "Region", accessor: (row) => row.region, rowSpan: true },
  { id: "oncall", header: "On call", accessor: (row) => row.oncall },
];

export default function Example() {
  return (
    <div className="w-full p-6">
      <DataTable data={data} columns={columns} getRowId={(row) => row.id} groupBy="team" border theme="dark" />
    </div>
  );
}

Column groups + freeze

Variant 5 of 19
H1H2
Alex Kim4251904860105Platform
Sara Chen3847885258102Product
Jordan Lee5144954763110Growth
Maya Patel4555925061108Platform
"use client";
import { DataTable, type DataTableColumn } from "@/components/ui/data-table";

interface ReportRow {
  id: string;
  name: string;
  q1: number;
  q2: number;
  q3: number;
  q4: number;
  owner: string;
}

const data: ReportRow[] = [
  { id: "1", name: "Alex Kim", q1: 42, q2: 51, q3: 48, q4: 60, owner: "Platform" },
  { id: "2", name: "Sara Chen", q1: 38, q2: 47, q3: 52, q4: 58, owner: "Product" },
  { id: "3", name: "Jordan Lee", q1: 51, q2: 44, q3: 47, q4: 63, owner: "Growth" },
];

const columns: DataTableColumn<ReportRow>[] = [
  { id: "name", header: "Name", accessor: (row) => row.name, freeze: "left", width: 140 },
  {
    id: "h1",
    header: "H1",
    accessor: () => null,
    columns: [
      { id: "q1", header: "Q1", accessor: (row) => row.q1, align: "right" },
      { id: "q2", header: "Q2", accessor: (row) => row.q2, align: "right" },
    ],
  },
  {
    id: "h2",
    header: "H2",
    accessor: () => null,
    columns: [
      { id: "q3", header: "Q3", accessor: (row) => row.q3, align: "right" },
      { id: "q4", header: "Q4", accessor: (row) => row.q4, align: "right" },
    ],
  },
  { id: "owner", header: "Owner", accessor: (row) => row.owner },
];

export default function Example() {
  return (
    <div className="w-full p-6">
      <DataTable data={data} columns={columns} getRowId={(row) => row.id} sortable border theme="dark" />
    </div>
  );
}

Freeze one left column

Variant 6 of 19

Scroll horizontally to keep frozen columns in view.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Scroll horizontally to keep frozen columns in view.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150, freeze: "left" },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130 }
      ]}
      />
    </div>
  );
}

Freeze one right column

Variant 7 of 19

Scroll horizontally to keep frozen columns in view.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Scroll horizontally to keep frozen columns in view.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150 },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130, freeze: "right" }
      ]}
      />
    </div>
  );
}

Freeze multiple left columns

Variant 8 of 19

Scroll horizontally to keep frozen columns in view.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Scroll horizontally to keep frozen columns in view.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150, freeze: "left" },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140, freeze: "left" },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130 }
      ]}
      />
    </div>
  );
}

Freeze multiple right columns

Variant 9 of 19

Scroll horizontally to keep frozen columns in view.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Scroll horizontally to keep frozen columns in view.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150 },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130, freeze: "right" },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130, freeze: "right" }
      ]}
      />
    </div>
  );
}

Freeze multiple columns on both edges

Variant 10 of 19

Scroll horizontally to keep frozen columns in view.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Scroll horizontally to keep frozen columns in view.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable selectable expandable renderExpanded={(row: Row) => <div>Details for {row.name}</div>}
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150, freeze: "left" },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140, freeze: "left" },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130, freeze: "right" },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130, freeze: "right" }
      ]}
      />
    </div>
  );
}

Virtualized rows + frozen columns

Variant 11 of 19

Scroll horizontally to keep frozen columns in view.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 1000 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Scroll horizontally to keep frozen columns in view.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        preset="enterprise"
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150, freeze: "left" },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140, freeze: "left" },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130, freeze: "right" },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130, freeze: "right" }
      ]}
      />
    </div>
  );
}

Frozen column groups

Variant 12 of 19

Scroll horizontally to keep frozen columns in view.

IdentityWorkflow
Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Scroll horizontally to keep frozen columns in view.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable
        stickyHeader maxHeight={320} border theme="dark"
        columns={[ { id: "identity", header: "Identity", accessor: () => null, freeze: "left", columns: [{ id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150, freeze: "left" }, { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140, freeze: "left" }] }, { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 }, { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 }, { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` }, { id: "workflow", header: "Workflow", accessor: () => null, freeze: "right", columns: [{ id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130, freeze: "right" }, { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130, freeze: "right" }] } ]}
      />
    </div>
  );
}

Pinned rows + frozen columns

Variant 13 of 19

Pinned rows + frozen columns.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Pinned rows + frozen columns.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable pinnedRows={["account-0", "account-1"]}
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150, freeze: "left" },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130, freeze: "right" }
      ]}
      />
    </div>
  );
}

Controlled selection + expansion

Variant 14 of 19

Controlled selection + expansion.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Details for Alex Kim
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { useState } from "react";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  const [selected, setSelected] = useState<string[]>(["account-0"]);
  const [expanded, setExpanded] = useState<string[]>(["account-0"]);
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Controlled selection + expansion.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable selectable expandable selectedIds={selected} onSelectionChange={setSelected} expandedIds={expanded} onExpandedChange={setExpanded} renderExpanded={(row: Row) => <div>Details for {row.name}</div>}
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150 },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130 }
      ]}
      />
    </div>
  );
}

Pagination + page sizes

Variant 15 of 19

Pagination + page sizes.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Pagination + page sizes.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable searchable paginated pageSize={5} pageSizeOptions={[5, 10, 20]}
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150 },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130 }
      ]}
      />
    </div>
  );
}

Sticky header

Variant 16 of 19

Sticky header.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Sticky header.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150 },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130 }
      ]}
      />
    </div>
  );
}

Light theme + borders

Variant 17 of 19

Light theme + borders.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Light theme + borders.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable
        stickyHeader maxHeight={320} border theme="light"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150 },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130 }
      ]}
      />
    </div>
  );
}

Custom header and body colors

Variant 18 of 19

Custom header and body colors.

Alex KimPlatformmember0@example.comNorth America$129.50PendingReview
Sara ChenProductmember1@example.comEurope$259.00ActiveReview
Jordan LeePlatformmember2@example.comAsia Pacific$388.50ActiveReview
Maya PatelProductmember3@example.comNorth America$518.00PendingReview
Alex KimPlatformmember4@example.comEurope$647.50ActiveReview
Sara ChenProductmember5@example.comAsia Pacific$777.00ActiveReview
Jordan LeePlatformmember6@example.comNorth America$906.50PendingReview
Maya PatelProductmember7@example.comEurope$1036.00ActiveReview
Alex KimPlatformmember8@example.comAsia Pacific$1165.50ActiveReview
Sara ChenProductmember9@example.comNorth America$1295.00PendingReview
Jordan LeePlatformmember10@example.comEurope$1424.50ActiveReview
Maya PatelProductmember11@example.comAsia Pacific$1554.00ActiveReview
Alex KimPlatformmember12@example.comNorth America$1683.50PendingReview
Sara ChenProductmember13@example.comEurope$1813.00ActiveReview
Jordan LeePlatformmember14@example.comAsia Pacific$1942.50ActiveReview
Maya PatelProductmember15@example.comNorth America$2072.00PendingReview
Alex KimPlatformmember16@example.comEurope$2201.50ActiveReview
Sara ChenProductmember17@example.comAsia Pacific$2331.00ActiveReview
Jordan LeePlatformmember18@example.comNorth America$2460.50PendingReview
Maya PatelProductmember19@example.comEurope$2590.00ActiveReview
Alex KimPlatformmember20@example.comAsia Pacific$2719.50ActiveReview
Sara ChenProductmember21@example.comNorth America$2849.00PendingReview
Jordan LeePlatformmember22@example.comEurope$2978.50ActiveReview
Maya PatelProductmember23@example.comAsia Pacific$3108.00ActiveReview
"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 24 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Custom header and body colors.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable headerBackground="bg-slate-900" headerTextColor="text-white" bodyBackground="bg-slate-50" bodyTextColor="text-slate-900"
        stickyHeader maxHeight={320} border theme="light"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150 },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130 }
      ]}
      />
    </div>
  );
}

Empty table

Variant 19 of 19

Empty table.

"use client";
import { DataTable } from "@/components/ui/data-table";

export default function Example() {
  const rows = Array.from({ length: 0 }, (_, i) => ({
    id: `account-${i}`,
    name: ["Alex Kim", "Sara Chen", "Jordan Lee", "Maya Patel"][i % 4],
    team: ["Platform", "Product"][i % 2],
    email: `member${i}@example.com`,
    region: ["North America", "Europe", "Asia Pacific"][i % 3],
    revenue: (i + 1) * 129.5,
    status: i % 3 === 0 ? "Pending" : "Active",
    actions: "Review",
  }));
  type Row = (typeof rows)[number];
  return (
    <div className="min-w-0 w-full p-6">
      <p className="mb-3 text-sm text-neutral-500">Empty table.</p>
      <DataTable data={rows} getRowId={(row: Row) => row.id}
        sortable
        stickyHeader maxHeight={320} border theme="dark"
        columns={[
        { id: "name", header: "Name", accessor: (row: Row) => row.name, width: 150 },
        { id: "team", header: "Team", accessor: (row: Row) => row.team, width: 140 },
        { id: "email", header: "Email", accessor: (row: Row) => row.email, width: 250 },
        { id: "region", header: "Region", accessor: (row: Row) => row.region, width: 180 },
        { id: "revenue", header: "Revenue", accessor: (row: Row) => row.revenue, width: 140, align: "right", cell: (row: Row) => `$${row.revenue.toFixed(2)}` },
        { id: "status", header: "Status", accessor: (row: Row) => row.status, width: 130 },
        { id: "actions", header: "Actions", accessor: (row: Row) => row.actions, width: 130 }
      ]}
      />
    </div>
  );
}

Props

PropTypeDescription
dataT[]Typed row objects; the component is generic over your row type.
columnsDataTableColumn<T>[]Column definitions: id, header, accessor (raw value for sort/search/group), optional cell renderer, sortType, searchable, width, align, freeze, nested columns (column groups), and rowSpan. Set freeze: "left" or "right" on any number of leaf columns, or on a group to inherit it. Width is a preferred minimum; offsets use rendered widths. Keep frozen columns at their corresponding outer edges and leave enough viewport space for both sets.
getRowId(row: T) => stringRequired stable row id — selection, expansion, pinning, and windowing keys all hang off it.
preset"basic" | "advanced" | "enterprise"Feature bundle. basic: search + pagination + sort. advanced: adds multi-sort, multi-select, expansion, sticky header. enterprise: advanced minus pagination so large data auto-virtualizes. Any explicit prop overrides the preset.
searchablebooleanGlobal debounced (150ms) case-insensitive search over searchable columns' accessor values.
paginatedbooleanClient-side pagination; disables virtualization.
pageSizenumberRows per page when paginated.
pageSizeOptionsnumber[]Optional page-size selector values.
sortablebooleanHeader click cycles asc, desc, none. Typed comparators (number/date/string) inferred from accessor values or set per column via sortType.
multiSortbooleanShift-click adds secondary/tertiary sort keys with visible priority badges.
onSort(sort: DataTableSortRule[]) => voidCalled with the full ordered rule list whenever sorting changes.
selectablebooleanCheckbox column; the tri-state header checkbox selects all filtered rows, not just the visible page or window.
selectedIdsstring[]Controlled selection; omit for uncontrolled.
onSelectionChange(ids: string[]) => voidSelection change callback (fires in both controlled and uncontrolled modes).
expandablebooleanChevron toggle column; requires renderExpanded.
renderExpanded(row: T) => React.ReactNodeExpanded panel content; works in both engines (the virtualizer measures expanded height).
expandedIdsstring[]Controlled expansion; omit for uncontrolled.
onExpandedChange(ids: string[]) => voidExpansion change callback.
groupBystringColumn id to group rows by — collapsible group header rows; uses the standard engine.
virtualizedbooleanForce an engine. Default is automatic: virtualized when flat data exceeds virtualizeThreshold and no groupBy/rowSpan/pagination is active.
virtualizeThresholdnumberRow count above which flat data is windowed.
rowHeightnumberEstimated row height in px used for windowing math.
maxHeightnumber | stringScroll container height; the virtualized engine falls back to 480px when omitted.
stickyHeaderbooleanKeep the header row(s) fixed while scrolling vertically.
pinnedRowsstring[]Row ids kept visible directly below the header.
theme"light" | "dark"Theme for default header/body colors when not overridden.
borderbooleanShow table and cell borders.
headerTextColorstringTailwind class for header text, e.g. text-neutral-900.
bodyTextColorstringTailwind class for body cell text.
headerBackgroundstringTailwind class for header background, e.g. bg-neutral-100.
bodyBackgroundstringTailwind class for body background.
classNamestringAdditional classes on the root wrapper.