KQ-1311 · DATA/GRID
Data Grid
A data grid that keeps pace with the hand: rows arrive after a skeleton in a cascade on glide (row by row, on a diagonal wave of cells, or in one fade) and replay from the side you paged toward, a heading press morphs its sort glyph between a double chevron and an arrow on snap while the rows on the page glide to their new seats, and columns resize from a grip that follows the finger 1:1, rubber-bands past its limits and springs back on snap with the release velocity. Shift-click selects a range drawn as one band that stretches from the anchor on snap, editable cells open an editor that lifts out of the cell and land their new value on flick, and the header and first columns stay pinned while the body scrolls. It is a real role="grid" with one roving cell: arrows move, Page keys turn the page, Space selects, Shift with an arrow extends the range, Enter sorts a heading or edits a cell, and Shift with Left or Right resizes a column.
Receivables
Loading invoices
Arrow keys move between cells. Space selects a row, Shift with an arrow extends the selection. Enter sorts a heading or edits a cell. Shift with Left or Right resizes a heading's column, Shift with Enter fits it to its contents. Page Up and Page Down turn the page.
loading 42 invoices
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/data-grid
Props
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Row height and type: 32, 40 or 48px rows. Heights glide when it changes.
- cascade
- "rows" | "wave" | "none"
- default "rows"
- How a page arrives after the skeleton or a page turn: rows rising in order, cells on a diagonal wave from the top-left, or one fade.
- stripes
- boolean
- default false
- Paint alternate rows, so a wide row is easy to follow across. The pinned cells share the stripe.
- columns
- DataGridColumn[]
- default defaultDataGridColumns
- The columns in order: id, header, kind (text, number, money, date, status), width and its limits, align, sortable, editable, mono, relative dates, status options and tones, and format, validate and parse functions. The first is the row header and stays pinned.
- rows / defaultRows
- DataGridRow[]
- default defaultDataGridRows
- The records, each with an id and a field per column. Controlled or uncontrolled; the default is 42 seeded invoices.
- onRowsChange
- (rows: DataGridRow[]) => void
- default —
- Fires with every row after an edit is committed, or reverted after a failed save.
- onCellEdit
- (edit: DataGridEdit) => void | Promise<unknown>
- default —
- Fires when an edit is committed, with the row, column, new value and previous value. Return a promise to show the cell saving; a rejection puts the old value back.
- sort / defaultSort
- { columnId: string; direction: "asc" | "desc" } | null
- default null
- The sort, or null for the rows' own order. Controlled or uncontrolled.
- onSortChange
- (sort: DataGridSort | null) => void
- default —
- Fires from the heading press or key that changed the sort: ascending, descending, then off.
- selected / defaultSelected
- string[]
- default []
- The selected row ids. Controlled or uncontrolled.
- onSelectedChange
- (ids: string[]) => void
- default —
- Fires from the click, Shift-click or key that changed the selection.
- page / defaultPage
- number
- default 0
- The page shown, from 0. Controlled or uncontrolled.
- onPageChange
- (page: number) => void
- default —
- Fires from the button or key that turned the page.
- pageSize
- number
- default 8
- Rows per page.
- maxHeight
- number
- default 360
- Tallest the body grows, in px, before it scrolls under its pinned header.
- actions
- DataGridAction[]
- default []
- Buttons offered in the footer while rows are selected: id, label, an optional 14px icon and tone.
- onAction
- (actionId: string, rowIds: string[]) => void
- default —
- One of the actions was pressed, with the selected row ids.
- onColumnResize
- (columnId: string, width: number) => void
- default —
- Fires when a column's width settles after a drag, a double-click fit or a key.
- now
- number | Date
- default defaultDataGridNow
- Today, for relative dates such as "in 3d" and "5d ago".
- itemLabel
- { one: string; other: string }
- default { one: "row", other: "rows" }
- What one row is called in counts and announcements: "3 invoices selected".
- locale
- string
- default "en-US"
- The locale for numbers and money.
- currency
- string
- default "USD"
- The currency for money columns.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the rows have arrived. Loading shows a skeleton of one page; going to ready plays the arrival.
- onRetry
- () => void
- default —
- Try again was pressed after the rows failed to load.
- emptyLabel
- string
- default "Nothing here yet"
- What an empty grid says.
- title
- string
- default "Receivables"
- The heading over the grid.
- label
- string
- default —
- The grid's accessible name. Defaults to the title.
- sound
- boolean
- default false
- Play a click for sorts, toggles, page turns and saves, and a tick per row joining a range and per 16px of resize.
- disabled
- boolean
- default false
- Keeps the grid readable and navigable but refuses sorting, selecting, editing, resizing and paging.
- className
- string
- default —
- Classes for the outer panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| density | "compact" | "regular" | "roomy" | "regular" | Row height and type: 32, 40 or 48px rows. Heights glide when it changes. |
| cascade | "rows" | "wave" | "none" | "rows" | How a page arrives after the skeleton or a page turn: rows rising in order, cells on a diagonal wave from the top-left, or one fade. |
| stripes | boolean | false | Paint alternate rows, so a wide row is easy to follow across. The pinned cells share the stripe. |
| columns | DataGridColumn[] | defaultDataGridColumns | The columns in order: id, header, kind (text, number, money, date, status), width and its limits, align, sortable, editable, mono, relative dates, status options and tones, and format, validate and parse functions. The first is the row header and stays pinned. |
| rows / defaultRows | DataGridRow[] | defaultDataGridRows | The records, each with an id and a field per column. Controlled or uncontrolled; the default is 42 seeded invoices. |
| onRowsChange | (rows: DataGridRow[]) => void | — | Fires with every row after an edit is committed, or reverted after a failed save. |
| onCellEdit | (edit: DataGridEdit) => void | Promise<unknown> | — | Fires when an edit is committed, with the row, column, new value and previous value. Return a promise to show the cell saving; a rejection puts the old value back. |
| sort / defaultSort | { columnId: string; direction: "asc" | "desc" } | null | null | The sort, or null for the rows' own order. Controlled or uncontrolled. |
| onSortChange | (sort: DataGridSort | null) => void | — | Fires from the heading press or key that changed the sort: ascending, descending, then off. |
| selected / defaultSelected | string[] | [] | The selected row ids. Controlled or uncontrolled. |
| onSelectedChange | (ids: string[]) => void | — | Fires from the click, Shift-click or key that changed the selection. |
| page / defaultPage | number | 0 | The page shown, from 0. Controlled or uncontrolled. |
| onPageChange | (page: number) => void | — | Fires from the button or key that turned the page. |
| pageSize | number | 8 | Rows per page. |
| maxHeight | number | 360 | Tallest the body grows, in px, before it scrolls under its pinned header. |
| actions | DataGridAction[] | [] | Buttons offered in the footer while rows are selected: id, label, an optional 14px icon and tone. |
| onAction | (actionId: string, rowIds: string[]) => void | — | One of the actions was pressed, with the selected row ids. |
| onColumnResize | (columnId: string, width: number) => void | — | Fires when a column's width settles after a drag, a double-click fit or a key. |
| now | number | Date | defaultDataGridNow | Today, for relative dates such as "in 3d" and "5d ago". |
| itemLabel | { one: string; other: string } | { one: "row", other: "rows" } | What one row is called in counts and announcements: "3 invoices selected". |
| locale | string | "en-US" | The locale for numbers and money. |
| currency | string | "USD" | The currency for money columns. |
| status | "ready" | "loading" | "error" | "ready" | Whether the rows have arrived. Loading shows a skeleton of one page; going to ready plays the arrival. |
| onRetry | () => void | — | Try again was pressed after the rows failed to load. |
| emptyLabel | string | "Nothing here yet" | What an empty grid says. |
| title | string | "Receivables" | The heading over the grid. |
| label | string | — | The grid's accessible name. Defaults to the title. |
| sound | boolean | false | Play a click for sorts, toggles, page turns and saves, and a tick per row joining a range and per 16px of resize. |
| disabled | boolean | false | Keeps the grid readable and navigable but refuses sorting, selecting, editing, resizing and paging. |
| className | string | — | Classes for the outer panel. |