DataGrid
A virtualized, spreadsheet-style grid: server-ready windowed data, Excel-style per-column filters, cell/row/column selection with clipboard copy, drag-to-reorder & resize columns, frozen columns, saved views, CSV/XLSX export, and English/Portuguese chrome. These demos run fully client-side via useClientGridSource.
1,200 rowstotal 639,204.96 · avg 532.67
DataGrid
A virtualized spreadsheet-style data grid. It is server-oriented by contract — a sparse getRow accessor plus windowed block fetching — so it scales to millions of rows. For local arrays, useClientGridSource applies sorting/filtering in memory and returns the data props.
Usage
import * as React from "react"
import {
DataGrid,
defaultColumnState,
useClientGridSource,
type GridColumn,
type GridColumnState,
type GridFilterState,
type GridSortState,
} from "@/components/ui/data-grid"
type Row = { name: string; team: string; salary: number; joined: string; url: string }
const columns: GridColumn<Row>[] = [
{ id: "name", title: "Name", type: "text", width: 180, sortable: true,
filter: { conditions: "text", paramMap: { contains: "q" } } },
{ id: "team", title: "Team", type: "text", width: 140, sortable: true,
filter: { optionsKey: "team", paramMap: { csv: "team" } } },
{ id: "salary", title: "Salary", type: "number", width: 120, align: "right", sortable: true,
filter: { conditions: "number", paramMap: { range: { min: "min", max: "max" } } },
format: (v) => `$${v}` },
{ id: "joined", title: "Joined", type: "date", width: 120, sortable: true,
filter: { conditions: "date", paramMap: { range: { min: "from", max: "to" } } } },
// Frozen to the right, opens a link (or use onClick for an action):
{ id: "url", title: "", type: "text", width: 60, pinned: "right",
action: { icon: ExternalLink, label: "Open", href: (row) => row.url } },
]
function Example({ data }: { data: Row[] }) {
const [sorting, setSorting] = React.useState<GridSortState>({ id: "name", desc: false })
const [filters, setFilters] = React.useState<GridFilterState>({})
const [columnState, setColumnState] = React.useState<GridColumnState>(
() => defaultColumnState(columns),
)
const src = useClientGridSource({ rows: data, columns, sorting, filters, summaryColumn: "salary" })
return (
<DataGrid<Row>
columns={columns}
columnState={columnState}
onColumnStateChange={setColumnState}
rowCount={src.rowCount}
totalCount={src.totalCount}
getRow={src.getRow}
onViewportChange={src.onViewportChange}
sorting={sorting}
onSortingChange={setSorting}
filters={filters}
onFiltersChange={setFilters}
filterOptions={src.filterOptions}
summary={src.summary}
resetToken={src.resetToken}
toolbar={{
export: { fetchRows: src.fetchRows, filename: "people", totalCount: src.totalCount },
savedViews: { storageKey: "people-views" },
}}
language="en"
className="h-[560px]"
/>
)
}DataGrid props
| Prop | Type | Default | Description |
|---|---|---|---|
| columns* | GridColumn<TData>[] | — | Full column definitions. Actual display order/visibility/width is driven by columnState. |
| columnState* | GridColumnState | — | Controlled layout { order, hidden, widths }. Seed with defaultColumnState(columns). |
| onColumnStateChange* | (s: GridColumnState) => void | — | Layout setter — fired on reorder, hide/show, and resize. |
| rowCount* | number | — | Virtualized row count (already capped to the browsable window). |
| totalCount* | number | — | Uncapped filtered total, shown in the status bar. |
| getRow* | (index: number) => TData | undefined | — | Sparse row accessor — return undefined while a block is still loading. |
| onViewportChange* | (startRow: number, endRow: number) => void | — | Fires the visible index range; drives block fetching. No-op for in-memory sources. |
| sorting / onSortingChange* | GridSortState / (s) => void | — | Controlled sort: { id, desc } | null. Sorting runs on the data source, not in the grid. |
| filters / onFiltersChange* | GridFilterState / (f) => void | — | Controlled per-column filters (checkbox value lists + typed conditions). |
| filterOptions | Record<string, GridFilterOption[]> | — | Distinct-value lists for checkbox filters, keyed by each column's filter.optionsKey. |
| summary | GridStatusSummary | null | — | { count, total, mean } shown on the right of the status bar (e.g. from a currency column). |
| isFetching | boolean | — | Shows the status-bar spinner. |
| isRefreshing | boolean | — | Dims the body under a veil while a new sort/filter loads (keeps previous rows visible). |
| toolbar | GridToolbarConfig<TData> | — | Top toolbar: export, fullscreen, saved views, column picker. Omit to hide the toolbar. |
| resetToken | string | — | Change it on any sort/filter change to clear the selection and scroll to top without remounting. |
| language | "en" | "pt" | "en" | Locale for the chrome (toolbar, filter menus, status bar, toasts) and number formatting. |
| className | string | — | Applied to the outer element — set a height (e.g. h-[560px]) so the grid can virtualize. |
GridColumn fields
| Prop | Type | Default | Description |
|---|---|---|---|
| id* | string | — | Row-object key; also the sort id sent to the data source. |
| title* | string | — | Header label. |
| type* | "text" | "number" | "date" | — | Drives sorting, selection aggregates, and which filter condition operators are offered. |
| width* | number | — | Fixed column width in px (resizable at runtime). |
| align | "left" | "right" | "left" | Cell + header alignment. |
| sortable | boolean | false | Enables the sort shortcuts in the column menu. |
| filter | { optionsKey?, conditions?, paramMap } | — | Filter config. optionsKey → checkbox value list; conditions ('text' | 'number' | 'date') enables the typed condition section; paramMap declares the operators (contains/equals/range). |
| pinned | "right" | — | Freeze the column to the right edge in a sticky lane (mirror of the row-number gutter). Display-only — meant for action/link icons. |
| action | { icon, label, href?, onClick? } | — | Render an interactive icon instead of text — an <a> (href) or a <button> (onClick). The cell stays copyable using the raw value. |
| format | (value, row) => string | — | Display string for the cell (keep it cheap — cells render plain text). |
| copyValue | (value, row) => string | — | Raw value used for clipboard/export TSV (defaults to String(value)). |
GridToolbarConfig
| Prop | Type | Default | Description |
|---|---|---|---|
| export | { fetchRows, filename, totalCount } | — | Enables CSV/XLSX export. fetchRows(onProgress) returns the full filtered dataset (the grid only holds viewport blocks). |
| savedViews | { storageKey: string } | — | Enables saving/loading named filter+sort+layout snapshots to localStorage under storageKey. |
| fullscreen | boolean | true | Shows the fullscreen toggle (CSS overlay; Escape exits). |
useClientGridSource(opts)
| Prop | Type | Default | Description |
|---|---|---|---|
| rows* | TData[] | — | The full in-memory dataset. |
| columns* | GridColumn<TData>[] | — | Same columns passed to the grid — used to interpret filters and derive filterOptions. |
| sorting / filters* | GridSortState / GridFilterState | — | The controlled state; the hook applies them to rows in memory. |
| summaryColumn | string | — | Optional numeric column id — the hook computes { count, total, mean } for the status bar. |
| → returns | ClientGridSource<TData> | — | { getRow, rowCount, totalCount, onViewportChange, filterOptions, summary, resetToken, fetchRows } — spread straight onto DataGrid. |