Editing & selection
Custom editors
Replace a column's editor with your own component, parse typed or pasted text into values, and share settings across columns.
The built-in editors cover text, numbers, dates and dropdowns. When a column needs something else (a stepper, a star picker, a tag list), give it an editor component. When the editor is fine but the text needs reading (a currency sign, thousands separators), give it a parse function.
Double-click Qty for a stepper editor (↑ and ↓ step by 100, Enter saves), or type $1,250.50 into Price: parse turns it into 1250.5.
An editor component
A column’s editor replaces the built-in input. It receives value, row, column, seed (the key that started type-to-edit, or null) and two callbacks: commit(value) saves any value as it is (no parsing) and cancel() closes without saving. Escape cancels, and so does focus leaving the editor; the grid focuses the editor’s first focusable element when it opens.
<!-- Stepper.svelte -->
<script lang="ts">
import { untrack } from 'svelte';
import type { CellEditorProps } from 'bo-grid';
let { value, commit }: CellEditorProps = $props();
let qty = $state(untrack(() => Number(value) || 0));
</script>
<span role="spinbutton" tabindex="0" aria-label="Quantity" aria-valuenow={qty}
onkeydown={(e) => {
if (e.key === 'ArrowUp') qty += 100;
else if (e.key === 'ArrowDown') qty = Math.max(0, qty - 100);
else if (e.key === 'Enter') commit(qty);
}}>{qty}</span>
<button type="button" onclick={() => commit(qty)}>OK</button> import Stepper from './Stepper.svelte';
{ type: 'number', key: 'qty', header: 'Qty', editable: true, editor: Stepper } The committed value still goes through the column’s validate, then to onCellEdit. An editor also makes a display widget editable: rating, tags, badge and the other display types are read-only until they have one.
import RatingEditor from './RatingEditor.svelte';
{ type: 'rating', key: 'rating', header: 'Rating', editable: true, editor: RatingEditor } The spreadsheet example has a full star-picker editor: Spreadsheet.
Parsing typed and pasted text
parse(raw, row) turns text into the stored value. It runs for the built-in editor, paste and the fill handle, in place of the built-in number and date conversion. Return undefined (or NaN) to refuse the input:
{
type: 'price', key: 'price', header: 'Price', editable: true,
parse: (raw) => (/d/.test(raw) ? Number(raw.replace(/[^d.-]/g, '')) : undefined),
} validate then sees the parsed value.
Settings shared by every column
defaultColumn applies settings under every column. A column’s own fields win, and so does a registered cellType:
<Grid {rows} {columns} height={480} defaultColumn={{ resizable: false, tooltip: true }} /> To reuse a whole column recipe (format, editor, filter, width) under a name, register it with cellTypes; see Column types.
Reference
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 }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, '')) }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 }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'] }defaultColumn DefaultColumn Default undefinedSettings applied under every column — its own fields (and a registered cellType) win.
Example
<Grid {rows} {columns} height={480} defaultColumn={{ minWidth: 72, sortable: false }} />cellTypes Record<string, CellTypeDef> Default undefinedReusable column types, by name. A column with cellType: 'name' gets that entry's defaults (format, compare, align, filter, width, a component renderer…) under its own fields, and the built-in type named by extends for sorting, filtering and export.
Example
<Grid {rows} {columns} height={480}
cellTypes={{ qty: { extends: 'number', decimals: 0, width: 80, format: (v) => Number(v).toLocaleString() } }} />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)} />