Editing & selection
Inline editing
Edit cells in place with an editor matched to the column type, validate the value, and write it back to your rows.
Mark a column editable: true and its cells can be edited in place. The grid is controlled: it never writes to your data itself, but reports each change through onCellEdit, where you update the row (or send the change to a server first).
Double-click a cell, press Enter on it, or select it and start typing. Status opens a dropdown, and Qty refuses a negative number.
Making a column editable
<script lang="ts">
import { Grid, type ColumnDef } from 'bo-grid';
const columns: ColumnDef[] = [
{ type: 'text', key: 'symbol', header: 'Symbol' },
{ type: 'number', key: 'qty', header: 'Qty', editable: true },
{ type: 'text', key: 'note', header: 'Note', editable: true },
];
let rows = $state(initialRows);
</script>
<Grid {rows} {columns} height={480} onCellEdit={(e) => (e.row[e.column.key] = e.value)} /> Keep the rows in $state so the cell shows the new value once you write it. The event carries the row, the column and the new value.
Opening and closing the editor
- Double-click a cell, or press Enter or F2 on the focused cell.
- Start typing on a focused cell: the editor opens with that character, as in a spreadsheet. (Date and dropdown editors open with Enter or a double-click.)
- Enter or leaving the cell saves; Tab saves and moves to the next cell (on to the next row at the end of one); Escape cancels. The grid keeps the keyboard afterwards.
- Picking an option in a dropdown with the pointer saves at once; with the arrow keys, Enter saves.
- What you type is yours until you save: a live update to the cell does not overwrite it.
The editor for each type
The editor matches the column:
| Column | Editor | e.value |
|---|---|---|
Number types (number, price, percent, volume, currency, heatmap) | a text input with a numeric keypad, so - and . type as expected | a number; a blank, or text that is not a number, is refused |
date | the browser’s date picker | epoch milliseconds |
Any column with options | a dropdown | the chosen string |
| Everything else | text input | the typed string |
Display widgets (badge, tags, rating, progress and others) and computed columns (value) are not editable by themselves; give a display column an editor to edit it. See Custom editors.
Dropdowns
Give an editable column options to pick from a list instead of typing:
{ type: 'text', key: 'status', header: 'Status', editable: true, options: ['New', 'Active', 'Closed'] } Validation
validate(value, row) runs before a value is saved; return false to refuse it and keep the old value. It sees the parsed value (a number for a number column), and it applies to paste and the fill handle too:
{ type: 'number', key: 'qty', header: 'Qty', editable: true, validate: (v) => Number(v) >= 0 } To turn typed text into a value yourself (strip a currency sign, read “1.5k”), use parse; see Custom editors.
Undo, paste and fill
Edits, pastes and fills undo with Ctrl/⌘+Z and redo with Ctrl/⌘+Y (or Ctrl/⌘+Shift+Z); a paste or a fill is one step. Paste and the fill handle write through the same validation and call onCellEdit for every cell they change. See Cell selection & clipboard.
A full spreadsheet
Dropdowns, parsed salaries, a date editor, the fill handle and saved layouts together:
Reference
onCellEdit (e: CellEditEvent) => void Called when an editable cell is committed. Update your row data in here.
Example
<Grid {rows} {columns} height={480} onCellEdit={({ row, column, value }) => (row[column.key] = value)} />fillHandle boolean Default falseShow an Excel-style fill handle at the selection's bottom-right corner; drag it to copy the selected value(s) across the extended range (editable columns only). In-memory mode only.
editable boolean Default falseAllow inline editing (double-click, Enter or F2 on the focused cell, or type to replace). Number types edit as numbers, date as a date. Display types (badge, boolean, rating, progress, tags, link, avatar, relative, sparkline, custom) edit only with an editor; a computed (value) column never does.
options string[] Default undefinedEditable choices: when set, editing renders a <select> of these options instead of a text input (enum/status columns).
Example
{ type: 'text', key: 'side', header: 'Side', editable: true, options: ['Buy', 'Sell'] }validate (value: unknown, row: GridRow) => boolean Validate an edited value before it commits (inline edit, custom editor, paste, fill). Return false to reject and keep the old value. Receives the parsed value.
Example
{ type: 'number', key: 'qty', header: 'Qty', editable: true, decimals: 0, validate: (v) => Number(v) > 0 && Number(v) % 100 === 0 }parse (raw: string, row: GridRow) => unknown Turn typed or pasted text into the stored value (runs before the built-in number/date coercion, which it replaces). Return undefined or a non-finite number to reject the input.
Example
{ type: 'number', key: 'qty', header: 'Qty', editable: true, parse: (raw) => Number(raw.replace(/[,\s]/g, '')) }editor Component<CellEditorProps> Svelte component that edits this cell in place of the built-in input. Makes any column type editable (with editable: true), including display widgets like rating or tags. It calls commit(value) to save — any value, no parsing — or cancel(); Escape cancels.
Example
{ type: 'rating', key: 'score', header: 'Score', editable: true, editor: StarPicker }