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.

Date
Reference
Merchant
Category
Account
Status
Amount
Receipt
1,200 rows

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

PropTypeDefaultDescription
columns*GridColumn<TData>[]Full column definitions. Actual display order/visibility/width is driven by columnState.
columnState*GridColumnStateControlled layout { order, hidden, widths }. Seed with defaultColumnState(columns).
onColumnStateChange*(s: GridColumnState) => voidLayout setter — fired on reorder, hide/show, and resize.
rowCount*numberVirtualized row count (already capped to the browsable window).
totalCount*numberUncapped filtered total, shown in the status bar.
getRow*(index: number) => TData | undefinedSparse row accessor — return undefined while a block is still loading.
onViewportChange*(startRow: number, endRow: number) => voidFires the visible index range; drives block fetching. No-op for in-memory sources.
sorting / onSortingChange*GridSortState / (s) => voidControlled sort: { id, desc } | null. Sorting runs on the data source, not in the grid.
filters / onFiltersChange*GridFilterState / (f) => voidControlled per-column filters (checkbox value lists + typed conditions).
filterOptionsRecord<string, GridFilterOption[]>Distinct-value lists for checkbox filters, keyed by each column's filter.optionsKey.
summaryGridStatusSummary | null{ count, total, mean } shown on the right of the status bar (e.g. from a currency column).
isFetchingbooleanShows the status-bar spinner.
isRefreshingbooleanDims the body under a veil while a new sort/filter loads (keeps previous rows visible).
toolbarGridToolbarConfig<TData>Top toolbar: export, fullscreen, saved views, column picker. Omit to hide the toolbar.
resetTokenstringChange 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.
classNamestringApplied to the outer element — set a height (e.g. h-[560px]) so the grid can virtualize.

GridColumn fields

PropTypeDefaultDescription
id*stringRow-object key; also the sort id sent to the data source.
title*stringHeader label.
type*"text" | "number" | "date"Drives sorting, selection aggregates, and which filter condition operators are offered.
width*numberFixed column width in px (resizable at runtime).
align"left" | "right""left"Cell + header alignment.
sortablebooleanfalseEnables 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) => stringDisplay string for the cell (keep it cheap — cells render plain text).
copyValue(value, row) => stringRaw value used for clipboard/export TSV (defaults to String(value)).

GridToolbarConfig

PropTypeDefaultDescription
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.
fullscreenbooleantrueShows the fullscreen toggle (CSS overlay; Escape exits).

useClientGridSource(opts)

PropTypeDefaultDescription
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 / GridFilterStateThe controlled state; the hook applies them to rows in memory.
summaryColumnstringOptional numeric column id — the hook computes { count, total, mean } for the status bar.
→ returnsClientGridSource<TData>{ getRow, rowCount, totalCount, onViewportChange, filterOptions, summary, resetToken, fetchRows } — spread straight onto DataGrid.