# Data Table Toolbar

> Search, filters, column visibility, export, and bulk actions for selected rows.

- Category: Data Display
- Kind: component
- Page: https://uaiblocks.dev/components/data-table-toolbar
- Registry item: https://uaiblocks.dev/r/data-table-toolbar.json
- Source: https://github.com/victor-teles/uai/blob/main/src/registry/uai/components/data-table-toolbar.tsx

## Install

```bash
bunx shadcn@latest add https://uaiblocks.dev/r/data-table-toolbar.json
```

## Usage

```tsx
"use client";

import { useState } from "react";
import {
  DataTableToolbar,
  DataTableToolbarBulkActions,
  DataTableToolbarButton,
  DataTableToolbarClearSelection,
  DataTableToolbarColumn,
  DataTableToolbarColumns,
  DataTableToolbarExport,
  DataTableToolbarGroup,
  DataTableToolbarSearch,
  DataTableToolbarSelectionCount,
  type DataTableToolbarVariant,
} from "@/components/ui/uai/data-table-toolbar";

const invoices = [
  { id: "INV-2041", customer: "Northwind Logistics", status: "Overdue", amount: "$4,820.00" },
  { id: "INV-2042", customer: "Lumen Studio", status: "Paid", amount: "$1,260.00" },
  { id: "INV-2043", customer: "Harbor & Pine", status: "Draft", amount: "$932.50" },
];

const statusTone: Record<string, string> = {
  Overdue: "var(--uai-danger)",
  Paid: "var(--uai-success)",
  Draft: "var(--uai-muted)",
};

export function DataTableToolbarPreview({
  variant = "toolbar",
}: {
  variant?: DataTableToolbarVariant;
}) {
  const [selected, setSelected] = useState<string[]>(["INV-2041", "INV-2043"]);
  const [overdueOnly, setOverdueOnly] = useState(false);
  const [columns, setColumns] = useState(["customer", "status", "amount"]);
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <DataTableToolbar
        variant={variant}
        selectedCount={selected.length}
        onClearSelection={() => setSelected([])}
      >
        <DataTableToolbarSearch label="Search invoices" placeholder="Search invoices…" />
        <DataTableToolbarGroup>
          <DataTableToolbarButton
            aria-pressed={overdueOnly}
            onClick={() => setOverdueOnly((current) => !current)}
          >
            Overdue only
          </DataTableToolbarButton>
          <DataTableToolbarColumns value={columns} onValueChange={setColumns}>
            <DataTableToolbarColumn value="customer">Customer</DataTableToolbarColumn>
            <DataTableToolbarColumn value="status">Status</DataTableToolbarColumn>
            <DataTableToolbarColumn value="amount">Amount</DataTableToolbarColumn>
            <DataTableToolbarColumn value="due">Due date</DataTableToolbarColumn>
          </DataTableToolbarColumns>
          <DataTableToolbarExport />
        </DataTableToolbarGroup>
        <DataTableToolbarBulkActions>
          <DataTableToolbarSelectionCount />
          <DataTableToolbarButton>Send reminder</DataTableToolbarButton>
          <DataTableToolbarButton>Mark as paid</DataTableToolbarButton>
          <DataTableToolbarClearSelection />
        </DataTableToolbarBulkActions>
      </DataTableToolbar>
      <fieldset style={{ display: "grid", gap: 2, margin: 0, padding: 0, border: 0, fontSize: 13 }}>
        <legend style={{ marginBottom: 6, color: "var(--uai-subtle)", fontSize: 11.5 }}>
          Select invoices
        </legend>
        {invoices.map((invoice) => {
          const checked = selected.includes(invoice.id);
          return (
            <label
              key={invoice.id}
              style={{
                display: "grid",
                gridTemplateColumns: "16px 76px minmax(0, 1fr) auto 88px",
                gap: 12,
                alignItems: "center",
                minHeight: 36,
                padding: "0 10px",
                borderRadius: 8,
                background: checked
                  ? "color-mix(in oklab, var(--uai-accent) 8%, transparent)"
                  : "transparent",
                transition: "background-color 120ms ease-out",
                cursor: "pointer",
              }}
            >
              <input
                type="checkbox"
                checked={checked}
                onChange={(event) =>
                  setSelected((current) =>
                    event.target.checked
                      ? [...current, invoice.id]
                      : current.filter((id) => id !== invoice.id),
                  )
                }
                style={{ width: 14, height: 14, margin: 0, accentColor: "var(--uai-accent)" }}
              />
              <span style={{ color: "var(--uai-subtle)", fontSize: 12 }}>{invoice.id}</span>
              <span style={{ fontWeight: 500 }}>{invoice.customer}</span>
              <span
                style={{
                  padding: "1px 8px",
                  borderRadius: 999,
                  background: `color-mix(in oklab, ${statusTone[invoice.status]} 14%, transparent)`,
                  color: statusTone[invoice.status],
                  fontSize: 11.5,
                  fontWeight: 500,
                }}
              >
                {invoice.status}
              </span>
              <span
                style={{ textAlign: "right", fontWeight: 500, fontVariantNumeric: "tabular-nums" }}
              >
                {invoice.amount}
              </span>
            </label>
          );
        })}
      </fieldset>
    </div>
  );
}
```

## Anatomy

- `DataTableToolbar`
  - `DataTableToolbarSearch`
  - `DataTableToolbarGroup`
    - `DataTableToolbarButton`
    - `DataTableToolbarColumns`
      - `DataTableToolbarColumn`
    - `DataTableToolbarExport`
  - `DataTableToolbarBulkActions`
    - `DataTableToolbarSelectionCount`
    - `DataTableToolbarClearSelection`

## Accessibility

- Search is a labelled native search input; Escape and the Clear button empty it and keep focus in the field.
- The Columns button is a disclosure with aria-expanded; its panel is a labelled popover with a fieldset of checkboxes, and Escape or an outside click closes it and returns focus.
- Bulk actions appear only while rows are selected, and a polite status region announces the selection count.
- Filtering, sorting, export, and row selection remain consumer-owned; the toolbar only exposes the controls.
