# Data Table

- Page: <https://kznlabs.github.io/labsUI/components/data-table>

## Data Table (`data-table`, component)

Table with columns as data: search across every column, sortable headers, checkbox selection that survives paging, a ⋯ menu per row, bulk actions in the toolbar and pagination. Built from Table, SearchInput, Checkbox, DropdownMenu, Pagination and Empty.

- Install: `npx @kznlabs/labsui add data-table`
- Docs: <https://kznlabs.github.io/labsUI/components/data-table>
- Brings: button, button-variants, checkbox, dropdown-menu, empty, field, input, kbd, label, list, overflow-focus, pagination, pagination-motion, platform, search-input, table, use-anchor, use-controllable-state, use-dismiss, use-hotkey, utils
- npm: clsx@^2.1.1, lucide-react@1.63.0, motion@14.7.1, tailwind-merge@^3.7.0

### API

- `<DataTable>`: Table with search, sortable columns, row selection, row actions and pagination, all on the
client. Columns are data: `{ id, header, accessor, cell?, sortable? }`. For server-driven
data, compose Table + Pagination yourself.
  - `columns: DataTableColumn<T>[]`: Column definitions in display order.
  - `data: T[]`: Rows; search, sort and paging happen on the client.
  - `getRowId: (row: T) => string`: Stable id per row; selection is kept by it across pages.
  - `label: string`: Names the table, the search box and each row's controls: "Members", "Select Jane".
  - `rowLabel: (row: T) => string`: Row name for screen readers, e.g. on its checkbox and ⋯ menu.
  - `defaultSort?: Sort`: Sort at first render.
  - `onSelectionChange?: (ids: string[]) => void`: Called with every selected row id.
  - `pageSize?: number` = `10`: Rows per page.
  - `rowActions?: (row: T) => ReactNode`: Items for the row's ⋯ menu (DropdownMenuItem and friends).
  - `searchable?: boolean` = `true`: Show a search field that filters every column.
  - `searchPlaceholder?: string` = `"Search…"`: Search field placeholder.
  - `selectable?: boolean` = `false`: Checkbox column; selection survives search and paging.
  - `selection?: string[]`: Controlled selected row ids; pair with `onSelectionChange`.
  - `striped?: boolean` = `false`: Alternate row backgrounds.
  - `toolbar?: (selected: T[]) => ReactNode`: Right side of the toolbar; receives the selected rows (bulk actions).

- `type Sort = { id: string; dir: "ascending" | "descending" }`
- `type DataTableColumn = { id: string; header: ReactNode; accessor?: (row: T) => string | number; cell?: (row: T) => ReactNode; sortable?: boolean; className?: string; }`

### Usage

```tsx
<DataTable label="Members" data={members} columns={[{ id: "name", header: "Member", accessor: (m) => m.name, sortable: true }]} getRowId={(m) => m.id} rowLabel={(m) => m.name} selectable rowActions={(m) => <DropdownMenuItem onSelect={() => kick(m)}>Kick</DropdownMenuItem>} />
```

### Example: members-data-table.tsx

```tsx
"use client";

import { Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { type DataTableColumn, DataTable } from "@/components/ui/data-table";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";

type Member = { id: string; name: string; messages: number };

const columns: DataTableColumn<Member>[] = [
  { id: "name", header: "Member", accessor: (m) => m.name, sortable: true },
  {
    id: "messages",
    header: "Messages",
    accessor: (m) => m.messages,
    sortable: true,
    className: "text-right tabular-nums",
  },
];

export function MembersDataTable({
  members,
  onKick,
}: {
  members: Member[];
  onKick: (ids: string[]) => void;
}) {
  return (
    <DataTable
      label="Members"
      data={members}
      columns={columns}
      getRowId={(m) => m.id}
      rowLabel={(m) => m.name}
      defaultSort={{ id: "messages", dir: "descending" }}
      selectable
      toolbar={(selected) =>
        selected.length > 0 && (
          <Button size="sm" variant="destructive" onClick={() => onKick(selected.map((m) => m.id))}>
            Kick {selected.length}
          </Button>
        )
      }
      rowActions={(m) => (
        <DropdownMenuItem variant="destructive" onSelect={() => onKick([m.id])}>
          <Trash2 />
          Kick
        </DropdownMenuItem>
      )}
    />
  );
}
```
