Data gridPro

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

Live · keyboard ready

Renewals

10,000 rows
Northwind Freight
Owen Park
Latin America
Closed won
40
$9,600
49%
Sep 23, 2026
Lumen Freight
Tyler Hayes
Europe
Closed won
105
$37,800
90%
Aug 28, 2027
Cedar Freight
Tyler Hayes
Latin America
Proposal
230
$96,600
64%
Oct 21, 2026
Brightline Freight
Marcus Johnson
Asia Pacific
Proposal
115
$41,400
78%
Sep 23, 2026
Harbor Freight
Daniel Kim
Latin America
Committed
10
$4,200
72%
Apr 5, 2027
Pinecrest Freight
Marcus Johnson
Europe
Closed won
470
$169,200
72%
Mar 25, 2027
Atlas Freight
Noah Bennett
Latin America
Discovery
45
$16,200
95%
Sep 13, 2026
Summit Freight
Owen Park
Asia Pacific
Negotiation
255
$107,100
93%
Oct 5, 2026
Fieldstone Freight
Marcus Johnson
Europe
Discovery
660
$237,600
82%
Feb 4, 2027
Marigold Freight
Noah Bennett
Europe
Negotiation
175
$73,500
44%
Mar 10, 2027
Keystone Freight
Emma Collins
Asia Pacific
Discovery
15
$4,500
76%
Sep 19, 2026
Redwood Freight
Priya Raman
North America
Discovery
230
$55,200
89%
Dec 21, 2026
Bluebird Freight
Owen Park
North America
Negotiation
730
$219,000
75%
May 14, 2027
Copper Creek Freight
Marcus Johnson
North America
Discovery
140
$42,000
80%
Oct 20, 2026
Silverline Freight
Hannah Walsh
Europe
Closed won
510
$153,000
78%
Aug 10, 2027
Oakridge Freight
Owen Park
North America
Committed
125
$52,500
72%
May 15, 2027
Beacon Freight
Marcus Johnson
Europe
Discovery
95
$28,500
42%
Nov 18, 2026
10,000 rows
A1Count1

  • Editing many values in place like a spreadsheet, such as budgets, price lists, or inventory.
  • Data people move to and from Excel or Google Sheets with copy and paste.
  • Work that needs range selection, a fill handle, fill down, and undo.
  • Large tables, up to tens of thousands of rows, that people filter, sort, group with subtotals, and total.
  • Server data too large to send at once, loaded in blocks as people scroll through a dataSource.
  • Finance tables with many period columns, date columns, and exports people open in Excel.
  • Bulk work on checked rows: copy, CSV export, delete, or your own actions.
  • Use sortable-data-table for read-only records with sorting and row selection.
  • Use a form when people edit one record at a time.
  • Use a card list or card grid when each record needs rich content like images or long text.

Installation

Install animui once, then import any component or block from its typed package entry.

animui · 250 components and blocks

React 19 and React DOM 19 are required. The package includes Free and Pro entries, styles and TypeScript declarations.

npm install https://animui.stracture.net/__local/package.tgz

Load the stylesheet once at your app root, then use the typed component import:

import 'animui/styles.css';
import { DataGrid } from 'animui/data-grid';

Read the Code tab for the implementation files and the API reference below for required props.

Download animui 0.3.0 · Agent guide
example.tsx
import { useState } from "react";import { Archive } from "lucide-react";import { DataGrid, type DataGridColumn, type DataGridRow, type DataGridSort } from "animui/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

6 parts. The first is the root.

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
columnsRequiredDataGridColumn[]–Column definitions in definition order. Pinned columns are drawn first.
labelRequiredstring–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
keyRequiredstring–Row field this column reads and writes.
labelRequiredstring–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
labelRequiredstring–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.
onActionRequired(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
getRowsRequired(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.
Arrow keys
Move the active cell.
Shift+arrows
Extend the range from the active cell.
Cmd/Ctrl+arrows
Jump to the first or last row or column. Add Shift to extend the range.
HomeorEnd
Move to the first or last column in the row.
Cmd/Ctrl+HomeorEnd
Move to the first or last cell of the grid.
PageUporPageDown
Move by one visible page of rows.
Cmd/Ctrl+A
Select every cell.
Ctrl+Space
Extend the range to the whole of its columns.
Shift+Space
Check or uncheck the rows in the range. Needs rowSelection.
EnterorF2
Edit the active cell, keeping its value. Arrow keys then move the caret.
Any character
Start editing and replace the value. Arrow keys then commit and move, like Excel.
Alt+ArrowDown
On a select cell, open its option list.
EnterorShift+Enter (editing)
Commit and move down or up. An invalid value stays open and is marked.
TaborShift+Tab (editing)
Commit and move right or left.
ArrowUporArrowDown (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.
Escape
Collapse the range to the active cell, or else clear checked rows.
DeleteorBackspace
Clear the range. Read-only cells are skipped.
Cmd/Ctrl+D
Fill down: copy the top row of the range into the rows below.
Cmd/Ctrl+CorXorV
Copy, cut, or paste. Works with Excel, Google Sheets, and Numbers.
Cmd/Ctrl+Z
Undo.
Cmd/Ctrl+Shift+ZorCmd/Ctrl+Y
Redo.
Shift+click header
Add the column as the next sort key. Plain click cycles ascending, descending, off.
Alt+ArrowLeftorArrowRight (header)
Narrow or widen the column by 16px.
EnterorArrowDown (searchorfilter input)
Return focus to the grid.
Escape (searchorfilter input)
Clear the input, or return to the grid when it is empty.
  • The grid is one focusable role="grid" with aria-multiselectable, aria-rowcount, aria-colcount, and aria-busy while loading. The active cell is exposed through aria-activedescendant, not moving focus.
  • Rows and cells carry aria-rowindex and aria-colindex, so counts stay right while rows are virtualized. The active and editing rows stay mounted when scrolled away.
  • Cells report aria-selected and aria-readonly. Rows report aria-selected when checked.
  • Headers are role="columnheader" with aria-sort. The sort button label names the direction, the sort priority, and the Alt + arrow resize keys.
  • The cell editor is labelled by column and row name and sets aria-invalid on a bad value. On select columns it is a combobox that controls a listbox, with aria-activedescendant on the highlighted option.
  • Row checkboxes are role="checkbox" and stay out of the tab order; the select-all box in the header is tabbable and shows a mixed state.
  • Number filter inputs describe their syntax and set aria-invalid on an unreadable expression. Select filters open a labelled checklist.
  • Edits, sorts, checks, copies, pastes, fills, undo, and redo are announced in a polite live region. The row count is also a polite live region.
  • Totals cells carry a full aria-label, such as "Q1 sum $15,200".
  • With groupBy the grid becomes role="treegrid". Group rows carry aria-level and aria-expanded, their toggle is a button that names the group, row count, and action, and subtotal cells carry full labels. Data rows carry aria-level too.
  • The date editor is a combobox with a dialog popup. The calendar opens in a portal so the grid never clips it, and focus returns to the cell when it closes.
  • Scrolling columns outside the view are not rendered, but every cell keeps its aria-colindex, and the active and editing cells stay mounted.
  • With a data source, rows still loading render as placeholder rows with aria-busy.
  • The selection rectangle and active cell spring between ranges on motionTokens.spring.snappy. Scrolling moves them at once, and resizing a column snaps them.
  • After a sort, rows slide to their new place on the spring curve for 700ms, and rows arriving from off screen fade in.
  • Pasted, filled, undone, and redone cells flash a wash that fades out over 0.9s.
  • The toolbar swaps to bulk actions with a short slide, fade, and blur. Status messages enter the same way.
  • Totals, the selection summary, and the checked count roll with animated-counter.
  • An invalid edit shakes once. Menus and the option list fade and scale in from their anchor.
  • A group's chevron turns on the spring curve as it opens and closes.
  • Reduced motion jumps the selection, drops the row slide, the shake, the press scales, the chevron turn, and the skeleton pulse, and keeps short fades.
  • The grid scrolls in both directions inside maxHeight. The header and totals row stay sticky, and pinned columns and the row gutter stay on the left.
  • Pinned columns are scaled down to about half of the grid width so a narrow grid still shows scrolling columns.
  • Layout reacts to the grid's own width with container queries. Below 640px toolbar buttons become icons. Below 460px the row count and undo and redo buttons hide. Below 480px the status message wraps to its own line.
  • On touch a pan scrolls the grid. A tap selects a cell on release, a second tap edits it, and only the fill handle takes over a touch drag.
  • Row checkboxes and header menu buttons are always visible on devices without hover.
  • Rows are virtualized: only the visible rows plus 6 above and below render, each positioned with translateY. Rows are memoized, so scrolling and typing re-render only the rows they touch.
  • Filters and search use useDeferredValue, so typing a filter stays responsive on large data.
  • The sorted and filtered view is recomputed only when sort, filters, search, visible columns, or the set of row ids change, not on every edit.
  • Select column options are derived from up to 200 distinct values when options is not given; pass options for large or wide data.
  • Columns are virtualized too: pinned columns always render, and scrolling columns render only within about 240px of the view, updated every 64px of horizontal scroll.
  • Group rows are computed with the view, not on every edit, so an edited row stays in its group until the next sort, filter, or grouping change. Subtotals update at once.
  • A dataSource keeps up to 40 blocks and aborts requests that a sort, filter, or search change makes stale. Undo holds up to 100 full row snapshots.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Choose it when people edit many values in place like a spreadsheet. Use sortable-data-table for read-only records. For rows that roll up under a shared value, such as journal lines by account and period, use groupBy.
  • Every row needs a unique, stable id. Column keys map to row fields. Keep rows from onRowsChange to persist edits.
  • Use number, currency, or percent for numeric data so parsing, right alignment, totals, and the status bar sum work. Store raw numbers, not formatted strings.
  • Mark computed or locked columns editable: false. Give select columns options so pasted and typed values are checked against them.
  • Copy writes tab separated text plus an HTML table with formatted values, so Excel and Sheets read typed numbers. Paste reads an HTML table first, then TSV with quoted cells. A selection that is a whole multiple of the clipboard is tiled; values that do not parse are skipped and reported.
  • The bulk Copy button copies checked rows with a header row through navigator.clipboard, and Export downloads CSV of checked rows. The toolbar Export downloads all filtered rows in view order.
  • A sort reorders once, like a spreadsheet. Editing a value does not re-sort or re-filter, so rows never jump under the cursor.
  • Undo keeps 100 steps and covers edits, fills, pastes, clears, and row deletes. Sort, filter, and column changes are view state and are not undone.
  • Text filters match accent-insensitive substrings; prefix = for an exact match or ! to exclude. The quick search matches every word across visible columns.
  • Filtering, sorting, and grouping run on the client over the rows you pass. For data too large to send at once, pass a dataSource: the grid asks for blocks of rows as people scroll and sends sort, filters, and search with each request.
  • Use a date column for days. Values stay ISO day strings, so they round-trip with no time zone shift; sort and filter compare them as days, and the fill handle continues a daily or monthly series.
  • Grouping reuses each column's aggregate for its subtotals, so one setting drives the group rows and the totals row. A grouped .xlsx export writes SUBTOTAL formulas, so Excel never counts a subtotal twice.
  • For a small form table, turn off rowNumbers when rows already carry their own number, and selectionStats when the address and counts mean nothing to the reader.
/components/data-grid/markdown

The full library index for assistants is at /llms.txt.