| #100000 | Avery S. | avery.s1@churchmediasquad.com | Youth | West | Beta | Volunteer | Paused | 2024-08-15 | 2026-04-10 | 7 | 208 | $3,151 | |
| #100001 | Riley T. | riley.t2@churchmediasquad.com | Worship | Online | Gamma | Staff | Archived | 2024-09-01 | 2026-04-23 | 8 | 149 | $1,801 | |
| #100002 | Cameron P. | cameron.p3@churchmediasquad.com | Hospitality | North | Delta | Director | Active | 2024-10-14 | 2026-04-09 | 9 | 90 | $452 | |
| #100003 | Drew K. | drew.k4@churchmediasquad.com | Outreach | South | Alpha | Lead | Paused | 2024-11-27 | 2026-04-22 | 10 | 31 | $5,102 | |
| #100004 | Sam M. | sam.m5@churchmediasquad.com | Kids | East | Beta | Coordinator | Archived | 2024-12-13 | 2026-04-08 | 11 | 212 | $3,753 | |
| #100005 | Taylor L. | taylor.l6@churchmediasquad.com | Communications | West | Gamma | Volunteer | Active | 2024-01-26 | 2026-04-21 | 12 | 153 | $2,403 | |
| #100006 | Morgan B. | morgan.b7@churchmediasquad.com | Production | Online | Delta | Staff | Paused | 2024-02-12 | 2026-04-07 | 13 | 94 | $1,054 | |
| #100007 | Casey R. | casey.r8@churchmediasquad.com | Youth | North | Alpha | Director | Archived | 2024-03-25 | 2026-04-20 | 14 | 35 | $3,064 | |
| #100008 | Reese S. | reese.s9@churchmediasquad.com | Worship | South | Beta | Lead | Active | 2024-04-11 | 2026-04-06 | 15 | 216 | $1,715 | |
| #100009 | Quinn T. | quinn.t10@churchmediasquad.com | Hospitality | East | Gamma | Coordinator | Paused | 2024-05-24 | 2026-04-19 | 16 | 157 | $365 | |
| #100010 | Hayden P. | hayden.p11@churchmediasquad.com | Hospitality | West | Delta | Volunteer | Archived | 2024-06-10 | 2026-04-05 | 17 | 98 | $5,016 | |
| #100011 | Jordan K. | jordan.k12@churchmediasquad.com | Outreach | Online | Alpha | Staff | Active | 2024-07-23 | 2026-04-18 | 18 | 39 | $3,666 | |
| #100012 | Avery M. | avery.m13@churchmediasquad.com | Kids | North | Beta | Director | Paused | 2024-08-09 | 2026-04-04 | 19 | 220 | $2,317 | |
| #100013 | Riley L. | riley.l14@churchmediasquad.com | Communications | South | Gamma | Lead | Archived | 2024-09-22 | 2026-04-17 | 0 | 161 | $967 | |
| #100014 | Cameron B. | cameron.b15@churchmediasquad.com | Production | East | Delta | Coordinator | Active | 2024-10-08 | 2026-04-03 | 1 | 102 | $5,618 |
60 total row(s).
Rows per page
Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/data-grid.jsonUsage
Pass `columns` and `data` directly to `<DataGrid>` — the component wires up sorting, filtering, pagination, and row selection through props. The data grid depends on `@tanstack/react-table` — install it alongside the master file.
$Terminal
npm install @tanstack/react-tableTSImport
import { DataGrid, type ColumnDef } from "@/components/ui/data-grid";TSExample
import { DataGrid, type ColumnDef } from "@/components/ui/data-grid";
type Project = {
id: string;
name: string;
status: "Active" | "Paused" | "Archived";
};
const PROJECTS: Project[] = [
{ id: "p_1", name: "Sermon series", status: "Active" },
{ id: "p_2", name: "Mid-week graphics", status: "Active" },
{ id: "p_3", name: "Volunteer recruitment", status: "Paused" },
];
const columns: ColumnDef<Project, unknown>[] = [
{ accessorKey: "name", header: "Project", meta: { label: "Project" } },
{ accessorKey: "status", header: "Status", meta: { label: "Status" } },
];
export function Example() {
return (
<DataGrid
columns={columns}
data={PROJECTS}
searchable="Filter projects..."
getRowId={(row) => row.id}
/>
);
}Composition
Anatomy of the DataGrid primitive.
// Flat one-liner — toolbar / table / pagination composed automatically
<DataGrid
columns={columns}
data={data}
searchable="Filter rows..." // false | true | placeholder string
selectable // prepends the checkbox column
hidableColumns // adds the View menu
paginated={{ pageSize: 10 }} // true | { pageSize, pageSizes }
scrollHeight="420px"
stickyHeader
getRowId={(row) => row.id}
/>
// Slot escape hatch — use when you need external table state access
// (e.g. reading rowSelection to drive a bulk-action toolbar)
import { DataGridRoot, useDataGrid, DataGridTable, DataGridToolbar } from "@/components/ui/data-grid";
const table = useDataGrid({ data, columns, features: { selection: true }, getRowId });
const selectedIds = Object.keys(table.getState().rowSelection ?? {});
<DataGridRoot table={table}>
{selectedIds.length > 0 && (
<DataGridToolbar>
<span>{selectedIds.length} selected</span>
</DataGridToolbar>
)}
<DataGridTable />
</DataGridRoot>
// All slot exports are retained as the escape hatch:
// DataGridRoot, DataGridToolbar, DataGridSearch, DataGridColumnVisibility,
// DataGridTable, DataGridPagination, DataGridSelectColumn, useDataGridPlain rows
Every feature off — just rows + columns rendered into the styled `<Table>`. Drop `<DataGridTable>` inside `<DataGrid>` and you're done.
| Project | Owner | Status | Spend | Updated |
|---|---|---|---|---|
| Sermon series — Easter | Jordan B. | Active | $4,320 | 2026-04-21 |
| Mid-week graphics | Avery K. | Active | $1,180 | 2026-04-20 |
| Volunteer recruitment | Riley S. | Paused | $0 | 2026-04-15 |
| Outreach video | Cameron L. | Active | $12,400 | 2026-04-19 |
| Annual report | Drew P. | Archived | $8,750 | 2026-03-30 |
| Kids check-in flow | Jordan B. | Active | $2,100 | 2026-04-18 |
| Stewardship campaign | Avery K. | Active | $6,220 | 2026-04-17 |
| Worship night promo | Sam R. | Active | $3,410 | 2026-04-16 |
| Hospitality refresh | Riley S. | Paused | $540 | 2026-04-12 |
| Online giving redesign | Cameron L. | Active | $9,800 | 2026-04-11 |
| Event landing pages | Drew P. | Archived | $2,900 | 2026-03-26 |
| Worship slides — May | Sam R. | Active | $700 | 2026-04-10 |
Sorting (with sticky header & scroll)
Wide 60×13 grid with the full feature stack: sortable + filterable headers, sticky `stickyHeader` while scrolling, hidable columns, row selection, and 15-row pagination. Scrollbars are hidden — drag/wheel scroll horizontally and vertically inside the rounded card.
| #100000 | Avery S. | avery.s1@churchmediasquad.com | Youth | West | Beta | Volunteer | Paused | 2024-08-15 | 2026-04-10 | 7 | 208 | $3,151 | |
| #100001 | Riley T. | riley.t2@churchmediasquad.com | Worship | Online | Gamma | Staff | Archived | 2024-09-01 | 2026-04-23 | 8 | 149 | $1,801 | |
| #100002 | Cameron P. | cameron.p3@churchmediasquad.com | Hospitality | North | Delta | Director | Active | 2024-10-14 | 2026-04-09 | 9 | 90 | $452 | |
| #100003 | Drew K. | drew.k4@churchmediasquad.com | Outreach | South | Alpha | Lead | Paused | 2024-11-27 | 2026-04-22 | 10 | 31 | $5,102 | |
| #100004 | Sam M. | sam.m5@churchmediasquad.com | Kids | East | Beta | Coordinator | Archived | 2024-12-13 | 2026-04-08 | 11 | 212 | $3,753 | |
| #100005 | Taylor L. | taylor.l6@churchmediasquad.com | Communications | West | Gamma | Volunteer | Active | 2024-01-26 | 2026-04-21 | 12 | 153 | $2,403 | |
| #100006 | Morgan B. | morgan.b7@churchmediasquad.com | Production | Online | Delta | Staff | Paused | 2024-02-12 | 2026-04-07 | 13 | 94 | $1,054 | |
| #100007 | Casey R. | casey.r8@churchmediasquad.com | Youth | North | Alpha | Director | Archived | 2024-03-25 | 2026-04-20 | 14 | 35 | $3,064 | |
| #100008 | Reese S. | reese.s9@churchmediasquad.com | Worship | South | Beta | Lead | Active | 2024-04-11 | 2026-04-06 | 15 | 216 | $1,715 | |
| #100009 | Quinn T. | quinn.t10@churchmediasquad.com | Hospitality | East | Gamma | Coordinator | Paused | 2024-05-24 | 2026-04-19 | 16 | 157 | $365 | |
| #100010 | Hayden P. | hayden.p11@churchmediasquad.com | Hospitality | West | Delta | Volunteer | Archived | 2024-06-10 | 2026-04-05 | 17 | 98 | $5,016 | |
| #100011 | Jordan K. | jordan.k12@churchmediasquad.com | Outreach | Online | Alpha | Staff | Active | 2024-07-23 | 2026-04-18 | 18 | 39 | $3,666 | |
| #100012 | Avery M. | avery.m13@churchmediasquad.com | Kids | North | Beta | Director | Paused | 2024-08-09 | 2026-04-04 | 19 | 220 | $2,317 | |
| #100013 | Riley L. | riley.l14@churchmediasquad.com | Communications | South | Gamma | Lead | Archived | 2024-09-22 | 2026-04-17 | 0 | 161 | $967 | |
| #100014 | Cameron B. | cameron.b15@churchmediasquad.com | Production | East | Delta | Coordinator | Active | 2024-10-08 | 2026-04-03 | 1 | 102 | $5,618 |
60 total row(s).
Rows per page
Global filter
Pair `features: { filtering: true }` with `<DataGridSearch>` inside the toolbar. Bound to TanStack's `globalFilter` so every visible column gets searched.
| Sermon series — Easter | Jordan B. | Active | $4,320 | 2026-04-21 |
| Mid-week graphics | Avery K. | Active | $1,180 | 2026-04-20 |
| Volunteer recruitment | Riley S. | Paused | $0 | 2026-04-15 |
| Outreach video | Cameron L. | Active | $12,400 | 2026-04-19 |
| Annual report | Drew P. | Archived | $8,750 | 2026-03-30 |
| Kids check-in flow | Jordan B. | Active | $2,100 | 2026-04-18 |
| Stewardship campaign | Avery K. | Active | $6,220 | 2026-04-17 |
| Worship night promo | Sam R. | Active | $3,410 | 2026-04-16 |
| Hospitality refresh | Riley S. | Paused | $540 | 2026-04-12 |
| Online giving redesign | Cameron L. | Active | $9,800 | 2026-04-11 |
| Event landing pages | Drew P. | Archived | $2,900 | 2026-03-26 |
| Worship slides — May | Sam R. | Active | $700 | 2026-04-10 |
Advanced filter (query builder)
Set `advancedFilter` and pass `renderAdvancedFilter={(table) => <DataGridAdvancedFilter table={table} />}` (install the `data-grid-advanced-filter` item) — the menu auto-populates from your `columns` + `data`: it infers each column's variant (text / number / date / select) and derives select options from the row values, no `meta.filter` required. Users stack typed rules — text, number (+ between), select, multi-select, date (+ between), boolean — combine them with an editable **AND / OR** join, nest **filter groups**, drag to reorder, and the search box keeps working alongside. Add `meta.filter` only to override (force `multiSelect`, supply custom options, etc.). Filtering is evaluated as a boolean tree over TanStack's global filter.
| Sermon series — Easter | Jordan B. | Active | $4,320 | 2026-04-21 |
| Mid-week graphics | Avery K. | Active | $1,180 | 2026-04-20 |
| Volunteer recruitment | Riley S. | Paused | $0 | 2026-04-15 |
| Outreach video | Cameron L. | Active | $12,400 | 2026-04-19 |
| Annual report | Drew P. | Archived | $8,750 | 2026-03-30 |
| Kids check-in flow | Jordan B. | Active | $2,100 | 2026-04-18 |
12 total row(s).
Rows per page
Row actions
Add a non-sorting, non-hidable `actions` column whose `cell` renders a squad `DropdownMenu` (popover-style menu) behind a ghost `…` button — copy / view / edit, plus a `variant="destructive"` delete. Each item's `onClick` runs your handler (here, a toast).
| Actions | |||||
|---|---|---|---|---|---|
| Sermon series — Easter | Jordan B. | Active | $4,320 | 2026-04-21 | |
| Mid-week graphics | Avery K. | Active | $1,180 | 2026-04-20 | |
| Volunteer recruitment | Riley S. | Paused | $0 | 2026-04-15 | |
| Outreach video | Cameron L. | Active | $12,400 | 2026-04-19 | |
| Annual report | Drew P. | Archived | $8,750 | 2026-03-30 | |
| Kids check-in flow | Jordan B. | Active | $2,100 | 2026-04-18 |
12 total row(s).
Rows per page
Editable cells
Set `editable` and give each editable column a `meta.editable` ({ variant, options }). Click a cell to edit — `text`/`number`/`url` show a type-constrained input (commit on Enter/blur, Esc cancels); `select` opens a squad `DropdownMenu`; `badge` shows a colored `Badge` + a searchable single-select combobox; `date` opens an inline Calendar + time popover; `avatars` shows an `AvatarGroup` and opens a searchable multi-select combobox. The grid is uncontrolled: handle `onCellEdit(rowId, columnId, value)` and update your `data`.
Row selection
Prepend `DataGridSelectColumn` to your columns array, set `features: { selection: true }`, and pass a stable `getRowId`. Read `table.getState().rowSelection` for the selected ids.
| Sermon series — Easter | Jordan B. | Active | $4,320 | 2026-04-21 | |
| Mid-week graphics | Avery K. | Active | $1,180 | 2026-04-20 | |
| Volunteer recruitment | Riley S. | Paused | $0 | 2026-04-15 | |
| Outreach video | Cameron L. | Active | $12,400 | 2026-04-19 | |
| Annual report | Drew P. | Archived | $8,750 | 2026-03-30 | |
| Kids check-in flow | Jordan B. | Active | $2,100 | 2026-04-18 | |
| Stewardship campaign | Avery K. | Active | $6,220 | 2026-04-17 | |
| Worship night promo | Sam R. | Active | $3,410 | 2026-04-16 | |
| Hospitality refresh | Riley S. | Paused | $540 | 2026-04-12 | |
| Online giving redesign | Cameron L. | Active | $9,800 | 2026-04-11 | |
| Event landing pages | Drew P. | Archived | $2,900 | 2026-03-26 | |
| Worship slides — May | Sam R. | Active | $700 | 2026-04-10 |
Floating bulk-action toolbar
Pass `bulkActions` to `<DataGrid>` and the grid renders a floating `<Toolbar>` from its own selection state — it slides up from the bottom-center of the viewport whenever one or more rows are checked. The callback receives `{ selectedIds, clearSelection }`; return the toolbar `items` (label, Archive / Delete actions, a trailing ✕ wired to `clearSelection`). Requires `selectable`; override the anchor with `bulkActionsPosition`.
| Sermon series — Easter | Jordan B. | Active | $4,320 | 2026-04-21 | |
| Mid-week graphics | Avery K. | Active | $1,180 | 2026-04-20 | |
| Volunteer recruitment | Riley S. | Paused | $0 | 2026-04-15 | |
| Outreach video | Cameron L. | Active | $12,400 | 2026-04-19 | |
| Annual report | Drew P. | Archived | $8,750 | 2026-03-30 | |
| Kids check-in flow | Jordan B. | Active | $2,100 | 2026-04-18 | |
| Stewardship campaign | Avery K. | Active | $6,220 | 2026-04-17 | |
| Worship night promo | Sam R. | Active | $3,410 | 2026-04-16 | |
| Hospitality refresh | Riley S. | Paused | $540 | 2026-04-12 | |
| Online giving redesign | Cameron L. | Active | $9,800 | 2026-04-11 | |
| Event landing pages | Drew P. | Archived | $2,900 | 2026-03-26 | |
| Worship slides — May | Sam R. | Active | $700 | 2026-04-10 |
0 selected
Column visibility
`features: { columnVisibility: true }` + `<DataGridColumnVisibility>` lets users hide columns. Set `enableHiding: false` on a column def to keep it pinned.
Hide columns to focus on what matters.
| Sermon series — Easter | Jordan B. | Active | $4,320 | 2026-04-21 |
| Mid-week graphics | Avery K. | Active | $1,180 | 2026-04-20 |
| Volunteer recruitment | Riley S. | Paused | $0 | 2026-04-15 |
| Outreach video | Cameron L. | Active | $12,400 | 2026-04-19 |
| Annual report | Drew P. | Archived | $8,750 | 2026-03-30 |
| Kids check-in flow | Jordan B. | Active | $2,100 | 2026-04-18 |
| Stewardship campaign | Avery K. | Active | $6,220 | 2026-04-17 |
| Worship night promo | Sam R. | Active | $3,410 | 2026-04-16 |
| Hospitality refresh | Riley S. | Paused | $540 | 2026-04-12 |
| Online giving redesign | Cameron L. | Active | $9,800 | 2026-04-11 |
| Event landing pages | Drew P. | Archived | $2,900 | 2026-03-26 |
| Worship slides — May | Sam R. | Active | $700 | 2026-04-10 |
Card section wrapper
Wrap the grid in a `<Card>` with a `<SectionHeader>` on top for a dashboard-style panel: bold title, optional inline action ('View all →'), and a hairline-free table flush against the card edges. Toggle `font="heading"` (Albra display face) or `font="sans"` (Inter) on the SectionHeader to match the surrounding page voice.
Active projects
| Sermon series — Easter | Jordan B. | Active | $4,320 | 2026-04-21 | |
| Mid-week graphics | Avery K. | Active | $1,180 | 2026-04-20 | |
| Volunteer recruitment | Riley S. | Paused | $0 | 2026-04-15 | |
| Outreach video | Cameron L. | Active | $12,400 | 2026-04-19 | |
| Annual report | Drew P. | Archived | $8,750 | 2026-03-30 |
12 total row(s).
Rows per page
Everything together
The full stack on one grid: global search, the rule-based **advanced filter**, sortable headers, row selection with a **floating bulk-action toolbar**, column visibility, pagination, **inline editable cells** (text/number/url/select/badge/date/avatars), and a per-row **actions menu**. The advanced filter auto-infers its fields from the data — no `meta.filter` needed.
| Actions | ||||||||
|---|---|---|---|---|---|---|---|---|
5 total row(s).
Rows per page
0 selected
API Reference
Props for the flat `<DataGrid>` component. For the compound escape-hatch API, see the slot subcomponent props below.
DataGrid
| Prop | Type | Default | Description |
|---|---|---|---|
data | TData[] | — | Row data array. |
columns | ColumnDef<TData, unknown>[] | — | TanStack column definitions. Use `meta.label` for human-readable names. |
searchable | boolean | string | false | Render the global search input. Pass a string to use it as the placeholder. |
advancedFilter | boolean | false | Enable the rule-based advanced filter logic (typed rules + AND/OR + nested groups). Render the menu by also passing `renderAdvancedFilter`. Coexists with `searchable`. |
renderAdvancedFilter | (table) => ReactNode | — | Render the advanced-filter menu in the toolbar — typically `(table) => <DataGridAdvancedFilter table={table} />`. The menu ships as the separate `data-grid-advanced-filter` item, so the core grid stays dependency-free until you opt in. Only used with `advancedFilter`. |
editable | boolean | false | Enable inline cell editing. Columns opt in via `meta.editable` ({ variant, options }). The grid is uncontrolled — handle `onCellEdit` and update your own `data`. |
onCellEdit | (rowId, columnId, value) => void | — | Fires when an editable cell commits. `value` is the parsed value (numbers for `number` cells). |
meta.editable.variant | "text" | "number" | "url" | "select" | "badge" | "date" | "avatars" | — | Per-column (on `ColumnDef.meta.editable`) — the editor + accepted input. `number` accepts numbers only; `url` types as a URL; `select` opens a `DropdownMenu` of `options`; `badge` renders a colored `Badge` and edits via a searchable single-select combobox (each option carries a `color`); `date` opens an inline Calendar + time popover (commits an ISO string); `avatars` shows an `AvatarGroup` and opens a searchable multi-select combobox (commits the selected values as `string[]`). |
meta.editable.options | { label; value }[] | — | Choices for a `select` editable cell. |
sortable | boolean | true | Enable/disable column sorting. |
paginated | boolean | { pageSize?: number; pageSizes?: number[] } | false | Enable pagination. Pass `true` for defaults (pageSize 10, pageSizes [5,10,20,50]) or an object to override. |
selectable | boolean | false | Prepend the checkbox select column and enable row selection. |
onSelectionChange | (selectedIds: string[]) => void | — | Fires when the row-selection set changes. Receives the selected row ids. |
bulkActions | (ctx: { selectedIds: string[]; clearSelection: () => void }) => ToolbarItem[] | — | Render a floating bulk-action `<Toolbar>` driven by the grid's own selection state — it slides in whenever rows are selected. Return the toolbar `items`; the callback gets the selected ids plus a `clearSelection` helper. Requires `selectable`. |
bulkActionsPosition | "bottom-center" | "top-center" | "bottom-right" | "bottom-left" | "bottom-center" | Where the `bulkActions` toolbar anchors. |
hidableColumns | boolean | false | Render the column-visibility (View) menu in the toolbar. |
scrollHeight | string | number | — | Constrain the table to a CSS max-height and add overflow-auto. |
stickyHeader | boolean | — | Pin `<thead>` to the top of the scroll container. |
getRowId | (row: TData, index: number) => string | — | Stable row id — required for selection survival across sorting/filtering. |
pageSize | number | 10 | Shorthand page size when `paginated` is `true`. Ignored when `paginated` is an object. |
emptyState | ReactNode | — | Rendered in the empty-state cell when no rows match. |
toolbarSlot | ReactNode | — | Extra content rendered right-aligned in the toolbar. |
card | boolean | { title?: string; titleFont?: "heading" | "sans"; titleColor?: "default" | "violet"; description?: string; action?: ReactNode; fullHeight?: boolean } | false | Wrap the grid in a dashboard `<Card>` with an optional SectionHeader (title + right-aligned action). When set, the inner table border is stripped so the table flows into the card edges. Defaults: `titleColor="violet"`, `titleFont="heading"`, `fullHeight=true`. |
className | string | — | Extra classes on the outermost wrapper div. |
useDataGrid (compound escape hatch)
| Prop | Type | Default | Description |
|---|---|---|---|
data | TData[] | — | Row data array. |
columns | ColumnDef<TData, unknown>[] | — | TanStack column definitions. Use `meta.label` for human-readable names. |
features | { sorting?, filtering?, pagination?, selection?, columnVisibility? } | all true except selection | Toggle the row models that are wired in. Disabled features render no-ops. |
pageSize | number | 10 | Initial page size when pagination is enabled. |
getRowId | (row: TData, index: number) => string | — | Stable row id — required for selection survival across re-fetches. |
DataGridAdvancedFilter
| Prop | Type | Default | Description |
|---|---|---|---|
table | TableInstance | — | The TanStack table from `useDataGrid({ advancedFilter: true })`. The flat `<DataGrid advancedFilter />` wires this for you. |
label | string | "Filter" | Trigger button label. |
disabled | boolean | — | Disable the trigger. |
meta.filter.variant | "text" | "number" | "date" | "select" | "multiSelect" | "boolean" | — | Optional override (on `ColumnDef.meta.filter`). When omitted, the variant is inferred from the column's data (numbers → number, dates → date, low-cardinality strings → select, else text). Every filterable column is offered in the builder. |
meta.filter.options | { label; value; icon?; count? }[] | — | Optional choices for `select` / `multiSelect`. When omitted, options are auto-derived from the distinct values in the row data. |
DataGridTable
| Prop | Type | Default | Description |
|---|---|---|---|
stickyHeader | boolean | — | Pin <thead> while the body scrolls. |
scrollHeight | string | number | — | Constrain the wrapper height and add overflow-auto on both axes. |
className | string | — | Extra classes on the table wrapper. |
DataGridToolbar
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Anything you want in the toolbar — search, filters, bulk actions. |
DataGridPagination
| Prop | Type | Default | Description |
|---|---|---|---|
pageSizes | number[] | [5, 10, 20, 50] | Selectable page sizes. |