Data display
Editable grid
A spreadsheet-style editable grid.
Client componentSource
Import
import { EditableGrid } from '@the_viveksingh/vivek-ui'An editable order table
Click a cell and use the arrow keys. Enter or F2 edits, typing replaces, Escape cancels. The whole grid is one tab stop, not one per cell.
Click a cell, then use the arrow keys. Enter or F2 edits; typing replaces. The whole grid is a single tab stop.
SKU
Product
Qty
Unit price
Total
VK-1001
Annual licence
12
$240.00
$2,880.00
VK-1002
Priority support
3
$1,200.00
$3,600.00
VK-1003
Onboarding workshop
1
$3,500.00
$3,500.00
VK-1004
Extra seats
25
$60.00
$1,500.00
Order total $11,480.00
const [rows, setRows] = useState(lines)
<EditableGrid
rows={rows}
label="Order lines"
getRowKey={(row) => row.id}
columns={[
{ key: 'sku', header: 'SKU', width: '7rem' },
{ key: 'product', header: 'Product', editable: true },
{
key: 'qty',
header: 'Qty',
editable: true,
numeric: true,
// Returning undefined rejects the edit - that is the whole validation API.
parse: (input) => (Number(input) >= 0 ? Number(input) : undefined),
},
{
key: 'price',
header: 'Unit price',
editable: true,
numeric: true,
// Displays formatted, edits raw. Conflating the two corrupts the value.
render: (row) => money(row.price),
format: (row) => String(row.price),
parse: (input) => Number(input.replace(/[^0-9.]/g, '')),
},
]}
// Nothing is mutated for you: the grid reports, your state decides.
onCellChange={({ rowIndex, columnKey, value }) =>
setRows((rows) =>
rows.map((row, i) => (i === rowIndex ? { ...row, [columnKey]: value } : row)),
)
}
/>`Read only
readOnly overrides the columns, for a permissions-gated view.
SKU
Product
Qty
VK-1001
Annual licence
12
VK-1002
Priority support
3
VK-1003
Onboarding workshop
1
VK-1004
Extra seats
25
<EditableGrid rows={rows} columns={columns} label="Order lines" readOnly />Props
Generated from the package's own type declarations, so this table cannot drift from the code.
| Prop | Type | Default | Description |
|---|---|---|---|
rows required | readonly Row[] | — | — |
columns required | readonly EditableColumn<Row>[] | — | — |
label required | string | — | Required. A grid with no accessible name is unusable with a screen reader. |
onCellChange | (change: CellChange<Row>) => void | — | Fired when an edit is committed. Nothing is mutated for you — apply it to your state. |
getRowKey | (row: Row, index: number) => string | number | — | Stable row identity. Defaults to the index, which breaks if rows reorder. |
readOnly | boolean | — | Blocks editing everywhere, whatever the columns say. |
height | string | number | — | Fixed height turns on scrolling with a sticky header. |
className | string | — | — |
id | string | — | — |
Rendering
Client component
EditableGrid declares 'use client' because it needs state, effects or event handlers. Importing it into a Server Component creates a client boundary at this component — everything above it stays on the server.