Xzero UI
Pro library

Data gridPro

A spreadsheet grid with range selection, inline editing, a fill handle, and animated sorting.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/data-grid
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/data-grid

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { useState } from "react";
import { Archive } from "lucide-react";
import { DataGrid, type DataGridColumn, type DataGridRow, type DataGridSort } from "@/components/xzero/data-grid/data-grid";

const columns: DataGridColumn[] = [
  { key: "item", label: "Item", width: 180, pinned: true },
  { key: "status", label: "Status", type: "select", options: ["Planned", "Active", "Done"] },
  { key: "q1", label: "Q1", type: "currency" },
  { key: "q2", label: "Q2", type: "currency" },
  { key: "margin", label: "Margin", type: "percent", decimals: 1, editable: false },
  { key: "notes", label: "Notes", hidden: true, aggregate: "count" },
];

export function Budget() {
  const [rows, setRows] = useState<DataGridRow[]>([
    { id: "r1", item: "Design", status: "Active", q1: 12000, q2: 14500, margin: 32.5, notes: "" },
    { id: "r2", item: "Hosting", status: "Planned", q1: 3200, q2: 3400, margin: 18, notes: "Annual plan" },
  ]);
  const [sort, setSort] = useState<DataGridSort[]>([{ key: "q1", dir: "desc" }]);
  return (
    <DataGrid
      label="Budget"
      columns={columns}
      rows={rows}
      onRowsChange={setRows}
      sort={sort}
      onSortChange={setSort}
      maxHeight={480}
      exportFileName="budget-2026"
      bulkActions={[{ label: "Archive", icon: <Archive size={16} aria-hidden />, onAction: checked => console.log(checked.map(r => r.id)) }]}
    />
  );
}

API reference

DataGrid

A spreadsheet grid for editing tabular data in place. It has range selection, a fill handle, Excel and Sheets clipboard, undo, multi-column sort, a filter row, pinned and hidden columns, row checks with bulk actions, a totals row, and virtualized rows. Every piece of view state works controlled or uncontrolled.

PropTypeDefaultDescription
columns (required)DataGridColumn[]–Column definitions in definition order. Pinned columns are drawn first.
label (required)string–Accessible name for the grid. Also names the exported CSV file.
rowsDataGridRow[]–Controlled rows. Pair with onRowsChange.
defaultRowsDataGridRow[]–Starting rows when uncontrolled.
onRowsChange(rows: DataGridRow[]) => void–Called with the full row list after every edit, fill, paste, cut, clear, delete, undo, or redo.
maxHeightnumber400Tallest the scrolling area grows before it scrolls, in px.
sortDataGridSort[]–Controlled sort keys in priority order.
defaultSortDataGridSort[][]Starting sort keys when uncontrolled.
onSortChange(sort: DataGridSort[]) => void–Called when a header click, Shift-click, or header menu changes the sort.
filtersDataGridFilters–Controlled column filters, keyed by column key.
defaultFiltersDataGridFilters{}Starting filters when uncontrolled. The filter row starts open when any filter is set.
onFiltersChange(filters: DataGridFilters) => void–Called when a filter changes or all filters are cleared.
selectedRowIdsstring[]–Controlled checked row ids.
defaultSelectedRowIdsstring[][]Starting checked row ids when uncontrolled.
onSelectedRowIdsChange(ids: string[]) => void–Called with checked ids in data order.
hiddenColumnsstring[]–Controlled hidden column keys.
defaultHiddenColumnsstring[]–Starting hidden keys. Defaults to columns with hidden: true.
onHiddenColumnsChange(keys: string[]) => void–Called when the columns menu or a header menu hides or shows a column.
pinnedColumnsstring[]–Controlled pinned column keys.
defaultPinnedColumnsstring[]–Starting pinned keys. Defaults to columns with pinned: true, or the first column when no column sets pinned.
onPinnedColumnsChange(keys: string[]) => void–Called when a header menu pins or unpins a column.
density"compact" | "standard" | "comfortable"–Controlled row density: 32, 40, or 48px rows.
defaultDensity"compact" | "standard" | "comfortable""standard"Starting density when uncontrolled.
onDensityChange(density: DataGridDensity) => void–Called when the density menu changes.
rowSelectionbooleantrueShows the row gutter with row numbers and checkboxes.
rowNumbersbooleantrueShows the row number in the gutter. When false the gutter narrows to 40px and its checkboxes always show. With rowSelection off there is no gutter either way.
selectionStatsbooleantrueShows the active cell address and the Count, Sum, and Average of the selected cells in the status bar. Status messages, such as Undo after a delete, show either way.
toolbarbooleantrueShows the toolbar with search, undo and redo, filters, columns, density, and export.
totalsbooleantrueShows the sticky totals row. It always covers every row the filters keep, including rows inside collapsed groups.
groupBystring[]–Controlled group-by column keys, outermost first. Each group row shows a toggle, its value and row count, and a subtotal under every column whose aggregate is not none.
defaultGroupBystring[][]Starting group-by keys when uncontrolled.
onGroupByChange(keys: string[]) => void–Called when a header menu starts or stops grouping by a column.
exportFormats("csv" | "xlsx")[]["csv"]Formats Export offers. With more than one, Export opens a menu. xlsx keeps number, currency, percent, and date formats, column widths, a frozen header and pinned columns, bold totals, and groups as outlined rows with SUBTOTAL formulas.
dataSourceDataGridDataSource–Loads rows from a server one block at a time as people scroll, instead of rows. Sort, filters, and search are sent with each request. Keep it stable with useMemo. A grid with a data source is read only, does not group, hides select all, and exports only checked rows.
bulkActionsDataGridBulkAction[]–Extra actions for checked rows, shown between Export and Delete.
canDeleteRowsbooleantrueShows Delete for checked rows. Deleting can be undone.
exportFileNamestring–Export file name without the extension. Defaults to a slug of label.
loadingbooleanfalseShows six placeholder rows and sets aria-busy.
emptyMessagestring"No rows yet"Shown when there are no rows at all. Filtered-out rows show "No rows match" with Clear filters.

DataGridColumn

One column definition.

PropTypeDefaultDescription
key (required)string–Row field this column reads and writes.
label (required)string–Header text. Also used in CSV and copied headers.
widthnumber140Starting width in px. The last column absorbs spare width on a wide grid.
minWidthnumber72Narrowest a resize can reach. Resizing stops at 560px.
type"text" | "number" | "currency" | "percent" | "select" | "date""text"Number, currency, and percent align right, parse typed numbers, and total in the footer. Select picks from options. Date stores an ISO day string such as "2026-01-31", shows it formatted, edits with a calendar, and refuses text that is not a real day.
dateFormatIntl.DateTimeFormatOptions{ month: "short", day: "numeric", year: "numeric" }How a date column shows its days, in en-US and UTC so a day never shifts with the time zone.
decimalsnumber0Decimal places when formatting numeric values.
editablebooleantruefalse makes the column read only. Edits, pastes, fills, and clears skip it.
optionsstring[]–Choices for a select column. Defaults to the distinct values already in the column. Also sets the sort order.
pinnedboolean–Starts pinned to the left edge. When no column sets this, the first column is pinned.
hiddenboolean–Starts hidden. People can show it again from the columns menu.
aggregate"sum" | "average" | "min" | "max" | "count" | "none"–What the totals row shows. Defaults to sum for number and currency, average for percent, none otherwise.
sortablebooleantruefalse disables the header sort button and the sort menu items.
filterablebooleantruefalse leaves the filter cell empty and drops Filter from the header menu.
groupableboolean–Offers Group by in the header menu. Defaults to true for text, select, and date columns and false for numeric ones.

DataGridBulkAction

An extra toolbar action for checked rows.

PropTypeDefaultDescription
label (required)string–Button text. Also the React key, so keep it unique.
iconReactNode–Icon before the label. The label hides below 640px of grid width.
tone"danger"–Draws the button in the danger color.
onAction (required)(rows: DataGridRow[]) => void–Receives the checked rows in data order.

DataGridDataSource

Server rows for DataGrid. The grid asks for the blocks that cover the rows in view, keeps up to 40 blocks, and starts over when sort, filters, or search change.

PropTypeDefaultDescription
getRows (required)(request: DataGridRowsRequest) => Promise<DataGridRowsResult>–Returns one block. request has start (inclusive), end (exclusive), sort, filters, search, and an AbortSignal that fires when the query changes. Resolve with rows, rowCount for the whole filtered set, and optional totals keyed by column key.
blockSizenumber100Rows per request.

Types

Exported from data-grid.tsx.

PropTypeDefaultDescription
DataGridValuestring | number | null–A cell value. Empty number cells store null.
DataGridRow{ id: string; [key: string]: DataGridValue }–A row. id must be unique and stable.
DataGridSort{ key: string; dir: "asc" | "desc" }–One sort key.
DataGridFiltersRecord<string, string | string[]>–Text and number columns take a query string. Select columns take the list of values to keep.
DataGridColumnType"text" | "number" | "currency" | "percent" | "select" | "date"–Column type.
DataGridRowsRequest{ start: number; end: number; sort: DataGridSort[]; filters: DataGridFilters; search: string; signal: AbortSignal }–What a data source is asked for.
DataGridRowsResult{ rows: DataGridRow[]; rowCount: number; totals?: Record<string, number | null> }–What a data source returns for one block.
DataGridExportFormat"csv" | "xlsx"–An export format.
DataGridExportNodeDataGridRow | { label: string; children: DataGridExportNode[] }–A row or a group of rows for toXlsx.
DataGridAggregate"sum" | "average" | "min" | "max" | "count" | "none"–Totals row aggregate.
DataGridDensity"compact" | "standard" | "comfortable"–Row density.
DataGridPropsinterface–Props of DataGrid.

data-grid-model helpers

Pure functions exported from data-grid-model.ts, usable on the server or in tests.

PropTypeDefaultDescription
format(value: DataGridValue | undefined, column: DataGridColumn) => string–Display text: en-US grouping, $ for currency, % for percent, a true minus sign for negatives.
parse(text: string, column: DataGridColumn, options?: string[]) => { ok: true; value: DataGridValue } | { ok: false }–Reads typed or pasted text. Numbers accept $, %, commas, spaces, and accounting parentheses. Select values match options without case.
parseDay / formatDay / isIsoDay(text: string) => string | null / (iso: string, options?) => string / (value: unknown) => boolean–Date helpers. parseDay reads 2026-01-31, 2026/1/31, Jan 31 2026, or 31 January 2026 and refuses all-number forms like 01/02/2026, which read differently by country.
dateFilter(expression: string) => ((iso: string) => boolean) | null | "invalid"–Parses a day, month (2026-03), or year (2026) with > >= < <= = !=, or a range like 2026-01-01..2026-03-31.
toXlsx(columns: DataGridColumn[], nodes: DataGridExportNode[], options?: DataGridXlsxOptions) => Uint8Array–From data-grid-xlsx.ts. Writes an .xlsx workbook with no dependencies. options: sheetName, freezeColumns, totals, widths. Files are stored without compression.
numberFilter(expression: string) => ((value: number) => boolean) | null | "invalid"–Parses >100, >=100, <5, !=0, =12, 12, or a range like 10..50.
rowMatcher(columns, filters, search, searchColumns?) => ((row: DataGridRow) => boolean) | null–One predicate for all filters and the quick search.
sortRows(rows: DataGridRow[], sort: DataGridSort[], columns: DataGridColumn[]) => DataGridRow[]–Stable multi-key sort. Empty cells sink to the bottom either way.
aggregate / defaultAggregate(rows, column, kind) => number | null / (column) => DataGridAggregate–The totals row math and its default per column type.
toTsv / parseTsv / toHtml / parseHtmlTable(matrix: string[][]) => string / (text: string) => string[][] / ...–Clipboard formats that match Excel and Google Sheets, including quoted cells with tabs or line breaks.
toCsv(columns: DataGridColumn[], rows: DataGridRow[]) => string–RFC 4180 CSV with a byte order mark so Excel reads UTF-8.
extend(source: DataGridValue[], count: number, forward: boolean, type?: DataGridColumnType) => DataGridValue[]–Fill handle values. An evenly spaced number run continues its series. In a date column, days continue by days, or by months when they share a day of the month or are all month ends. Anything else repeats.

Keyboard interactions

KeysAction
Arrow keysMove the active cell.
Shift + arrowsExtend the range from the active cell.
Cmd/Ctrl + arrowsJump to the first or last row or column. Add Shift to extend the range.
Home / EndMove to the first or last column in the row.
Cmd/Ctrl + Home / EndMove to the first or last cell of the grid.
PageUp / PageDownMove by one visible page of rows.
Cmd/Ctrl + ASelect every cell.
Ctrl + SpaceExtend the range to the whole of its columns.
Shift + SpaceCheck or uncheck the rows in the range. Needs rowSelection.
Enter / F2Edit the active cell, keeping its value. Arrow keys then move the caret.
Any characterStart editing and replace the value. Arrow keys then commit and move, like Excel.
Alt + ArrowDownOn a select cell, open its option list.
Enter / Shift + Enter (editing)Commit and move down or up. An invalid value stays open and is marked.
Tab / Shift + Tab (editing)Commit and move right or left.
ArrowUp / ArrowDown (option list)Move through select options. Enter or Tab picks the highlighted one.
Alt + ArrowDown (date editing)Open the calendar and move focus into it. Arrow keys pick a day, Enter saves it, Escape returns to the text.
Escape (editing)Close the option list or calendar, or cancel the edit.
EscapeCollapse the range to the active cell, or else clear checked rows.
Delete / BackspaceClear the range. Read-only cells are skipped.
Cmd/Ctrl + DFill down: copy the top row of the range into the rows below.
Cmd/Ctrl + C / X / VCopy, cut, or paste. Works with Excel, Google Sheets, and Numbers.
Cmd/Ctrl + ZUndo.
Cmd/Ctrl + Shift + Z / Cmd/Ctrl + YRedo.
Shift + click headerAdd the column as the next sort key. Plain click cycles ascending, descending, off.
Alt + ArrowLeft / ArrowRight (header)Narrow or widen the column by 16px.
Enter / ArrowDown (search or filter input)Return focus to the grid.
Escape (search or filter input)Clear the input, or return to the grid when it is empty.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in advanced charts

Guidance for AI tools

Data grid: A spreadsheet grid with range selection, inline editing, a fill handle, and animated sorting. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components