DataTable

A powerful data table with sorting, filtering, pagination, and selection.

$ npx @beaket/ui add data-table
Name
Email
Role
Status
Alice Johnsonalice@example.comAdmin active
Bob Smithbob@example.comEditor active
Carol Whitecarol@example.comViewer inactive
David Browndavid@example.comEditor pending
Eve Daviseve@example.comAdmin active
Showing 1 to 5 of 10 results
Source
import { CircleCheck, CircleMinus, Clock } from "lucide-react";

import { Badge } from "@/components/ui/badge";

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

// Sample data type
interface User {
  id: string;
  name: string;
  email: string;
  role: string;
  status: "active" | "inactive" | "pending";
}
// Sample data
const users: User[] = [
  { id: "1", name: "Alice Johnson", email: "alice@example.com", role: "Admin", status: "active" },
  { id: "2", name: "Bob Smith", email: "bob@example.com", role: "Editor", status: "active" },
  { id: "3", name: "Carol White", email: "carol@example.com", role: "Viewer", status: "inactive" },
  { id: "4", name: "David Brown", email: "david@example.com", role: "Editor", status: "pending" },
  { id: "5", name: "Eve Davis", email: "eve@example.com", role: "Admin", status: "active" },
];
// Column definitions
const columns: ColumnDef<User>[] = [
  {
    accessorKey: "name",
    header: "Name",
    size: 180,
  },
  {
    accessorKey: "email",
    header: "Email",
    size: 220,
  },
  {
    accessorKey: "role",
    header: "Role",
    size: 120,
  },
  {
    accessorKey: "status",
    header: "Status",
    size: 120,
    cell: ({ row }) => {
      const status = row.getValue("status") as User["status"];
      const variants = {
        active: "default",
        inactive: "secondary",
        pending: "outline",
      } as const;
      const icons = { active: CircleCheck, inactive: CircleMinus, pending: Clock } as const;
      const Icon = icons[status];
      return (
        <Badge variant={variants[status]} className="inline-flex w-24 justify-center">
          <Icon aria-hidden="true" className="mr-1 size-3" /> {status}
        </Badge>
      );
    },
  },
];

const args = {
  columns,
  data: [
    ...users,
    ...users.map((u, i) => ({
      ...u,
      id: `${u.id}-copy-${i}`,
      email: `${u.name.toLowerCase().replace(" ", ".")}.${i}@example.com`,
    })),
  ],
  searchable: true,
  searchPlaceholder: "Search users...",
  paginated: true,
  pageSize: 5,
  className: "[&_td]:!py-0.5",
  selectable: true,
  onSelectionChange: () => {},
};

export default function Example() {
  return <DataTable {...args} />;
}

Examples

All Features

Basic Table

Name
Email
Role
Status
Alice Johnsonalice@example.comAdmin active
Bob Smithbob@example.comEditor active
Carol Whitecarol@example.comViewer inactive

With Search

Name
Email
Role
Status
Alice Johnsonalice@example.comAdmin active
Bob Smithbob@example.comEditor active
Carol Whitecarol@example.comViewer inactive

With Selection

Name
Email
Role
Status
Alice Johnsonalice@example.comAdmin active
Bob Smithbob@example.comEditor active
Carol Whitecarol@example.comViewer inactive

Compact Mode

Name
Email
Role
Status
Alice Johnsonalice@example.comAdmin active
Bob Smithbob@example.comEditor active
Carol Whitecarol@example.comViewer inactive
Source
import { CircleCheck, CircleMinus, Clock } from "lucide-react";

import { Badge } from "@/components/ui/badge";

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

// Sample data type
interface User {
  id: string;
  name: string;
  email: string;
  role: string;
  status: "active" | "inactive" | "pending";
}
// Sample data
const users: User[] = [
  { id: "1", name: "Alice Johnson", email: "alice@example.com", role: "Admin", status: "active" },
  { id: "2", name: "Bob Smith", email: "bob@example.com", role: "Editor", status: "active" },
  { id: "3", name: "Carol White", email: "carol@example.com", role: "Viewer", status: "inactive" },
  { id: "4", name: "David Brown", email: "david@example.com", role: "Editor", status: "pending" },
  { id: "5", name: "Eve Davis", email: "eve@example.com", role: "Admin", status: "active" },
];
// Column definitions
const columns: ColumnDef<User>[] = [
  {
    accessorKey: "name",
    header: "Name",
    size: 180,
  },
  {
    accessorKey: "email",
    header: "Email",
    size: 220,
  },
  {
    accessorKey: "role",
    header: "Role",
    size: 120,
  },
  {
    accessorKey: "status",
    header: "Status",
    size: 120,
    cell: ({ row }) => {
      const status = row.getValue("status") as User["status"];
      const variants = {
        active: "default",
        inactive: "secondary",
        pending: "outline",
      } as const;
      const icons = { active: CircleCheck, inactive: CircleMinus, pending: Clock } as const;
      const Icon = icons[status];
      return (
        <Badge variant={variants[status]} className="inline-flex w-24 justify-center">
          <Icon aria-hidden="true" className="mr-1 size-3" /> {status}
        </Badge>
      );
    },
  },
];

export default () => (
  <div className="space-y-8">
    <div>
      <h3 className="mb-2 text-sm font-medium">Basic Table</h3>
      <DataTable columns={columns} data={users.slice(0, 3)} />
    </div>
    <div>
      <h3 className="mb-2 text-sm font-medium">With Search</h3>
      <DataTable
        columns={columns}
        data={users.slice(0, 3)}
        searchable
        searchPlaceholder="Search..."
      />
    </div>
    <div>
      <h3 className="mb-2 text-sm font-medium">With Selection</h3>
      <DataTable columns={columns} data={users.slice(0, 3)} selectable />
    </div>
    <div>
      <h3 className="mb-2 text-sm font-medium">Compact Mode</h3>
      <DataTable columns={columns} data={users.slice(0, 3)} compact />
    </div>
  </div>
);

Props

NameTypeDefaultDescription
labelsDataTableLabels | undefined—Accessible names and pagination summary; omitted labels retain English defaults.
columns*ColumnDef<TData, unknown>[]—Column definitions using TanStack Table's ColumnDef — see https://tanstack.com/table/latest/docs/guide/column-defs
data*TData[]—Array of data to display
searchableboolean | undefinedfalseEnable global search/filter functionality
searchPlaceholderstring | undefinedSearch...Search placeholder text
paginatedboolean | undefinedfalseEnable pagination
pageSizenumber | undefined10Number of rows per page
emptyMessagestring | undefinedNo data availableEmpty state message
emptyStateReactNode—Custom empty state component
enableSortingboolean | undefinedtrueEnable column sorting (default: true)
enableFilteringboolean | undefinedtrueEnable column filtering (default: true)
initialColumnVisibilityColumnVisibilityState | undefined—Initial column visibility state
compactboolean | undefinedfalseShow compact table styling
selectableboolean | undefinedfalseEnable row selection with checkboxes
onSelectionChange((selectedRows: TData[]) => void) | undefined—Callback when row selection changes
initialRowSelectionRowSelectionState | undefined—Initial row selection state
getRowClassName((row: TData) => string) | undefined—Function to generate custom class names for each row based on row data
onRowMouseEnter((row: TData) => void) | undefined—Callback when mouse enters a row
onRowMouseLeave((row: TData) => void) | undefined—Callback when mouse leaves a row
childrenReactNode | ((table: DataTableInstance<TData>) => ReactNode)—Arrange the parts yourself instead of letting the props lay them out. A function child receives the TanStack table instance (§11).
Toolbar.searchPlaceholderstringSearch...Placeholder for the default search field
Row.row*Row<TData>—The row this cell group renders. Cells are derived from it.
Empty.messagestringNo data availableMessage shown when no children are supplied