Skip to main content

Editing & Clipboard

Double-click a cell to edit it. Hit Enter to save, Escape to cancel. It feels like a spreadsheet because it's built to work like one — with clipboard paste, drag-to-fill, and full undo/redo out of the box.

Double-click any cell to edit, or press F2
Live
Try it in your framework

The demo above uses Radix UI for styling. Each framework renders editors with its native components — dropdowns, date pickers, and inputs that match your design system.

Get started in 5 minutes

Here's a realistic setup: an employee table with text, dropdown, and numeric columns — including validation that rejects bad salary values before they're saved.

import { OGrid, useUndoRedo } from '@alaarab/ogrid-react-radix';
import { useState } from 'react';

const DEPTS = ['Engineering', 'Marketing', 'Sales', 'Finance', 'Operations'];
const STATUSES = ['Active', 'Draft', 'Archived'];

const columns = [
{ columnId: 'name', name: 'Name', editable: true },
{
columnId: 'department',
name: 'Department',
editable: true,
cellEditor: 'richSelect',
cellEditorParams: { values: DEPTS },
},
{
columnId: 'status',
name: 'Status',
editable: true,
cellEditor: 'select',
cellEditorParams: { values: STATUSES },
},
{
columnId: 'salary',
name: 'Salary',
editable: true,
type: 'numeric',
valueParser: ({ newValue }) => {
const num = Number(newValue);
return isNaN(num) || num < 0 ? undefined : num; // undefined = reject
},
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
},
];

function App() {
const [data, setData] = useState(initialData);
const { handleCellValueChanged, undo, redo, canUndo, canRedo } = useUndoRedo({
data, setData, getRowId: (item) => item.id,
});

return (
<OGrid
columns={columns}
data={data}
getRowId={(item) => item.id}
editable
onCellValueChanged={handleCellValueChanged}
onUndo={undo}
onRedo={redo}
canUndo={canUndo}
canRedo={canRedo}
/>
);
}
Switching UI libraries

Same props across all React packages — just change the import:

  • Radix (lightweight, default): from '@alaarab/ogrid-react-radix'
  • Fluent UI (Microsoft 365 / SPFx): from '@alaarab/ogrid-react-fluent' - wrap in <FluentProvider>
  • Material UI (MUI v7): from '@alaarab/ogrid-react-material' - wrap in <ThemeProvider>

That single setup gives you inline editing, clipboard paste, fill handle, and full undo/redo.

Editing a cell

Double-click or press F2 to open the editor. When you're done:

  • Enter — saves and moves focus down
  • Tab — saves and moves focus right
  • Escape — cancels, restores the original value

Which editor should I use?

cellEditorWhen to use it
'text' (default)Freeform text — names, notes, anything open-ended
'select'Small, fixed list where the user picks exactly one value
'richSelect'Longer list with keyboard search — much nicer UX than a plain dropdown
'checkbox'Boolean toggle — renders as a checkbox, commits on click

Searchable dropdown (richSelect)

When your list gets longer than about 8 items, switch to richSelect. It adds a search box and full keyboard navigation:

{
columnId: 'category',
editable: true,
cellEditor: 'richSelect',
cellEditorParams: {
values: ['Engineering', 'Design', 'Marketing', 'Sales', 'Legal', 'Finance'],
formatValue: (v) => `Dept: ${v}`, // optional display formatting
},
}

Lock specific rows from editing

Use a function for editable to make it conditional:

{ columnId: 'name', editable: (item) => item.status !== 'locked' }

The cell still renders normally — it just won't open an editor when you click it.

Custom editor (popup)

For date pickers, color pickers, or any custom UI, pass your own component. Set cellEditorPopup: true to render it in a floating popover instead of inline:

function DateEditor({ value, onValueChange, onCommit, onCancel }: ICellEditorProps<Task>) {
return (
<input
type="date"
value={value as string}
onChange={(e) => onValueChange(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') onCommit();
if (e.key === 'Escape') onCancel();
}}
autoFocus
/>
);
}

// In column def:
{ columnId: 'dueDate', editable: true, cellEditor: DateEditor, cellEditorPopup: true }

Validating input with valueParser

valueParser runs on every edit, paste, fill, and delete. Return undefined to silently reject the value — the cell snaps back to its previous state:

{
columnId: 'age',
editable: true,
type: 'numeric',
valueParser: ({ newValue }) => {
const num = Number(newValue);
return isNaN(num) || num < 0 || num > 150 ? undefined : num;
},
}
Pro tip

valueParser is your single source of truth for validation — it runs on inline edits, clipboard paste, fill handle, and delete. You don't need separate logic for each.

Clipboard

Select cells, then Ctrl+C to copy, Ctrl+V to paste
Live

Standard shortcuts work on any selection — no configuration needed:

KeyActionRequires editable
Ctrl+C / Cmd+CCopy as tab-separated textNo
Ctrl+V / Cmd+VPaste into cellsYes
Ctrl+X / Cmd+XCut (copy then clear)Yes
Delete / BackspaceClear selected cellsYes

Copy respects valueFormatter — what you see is what you get in the clipboard. Paste calls valueParser on each value, so bad data is rejected before it lands in your rows.

Fill handle

Select a cell, then drag the green square at the bottom-right corner to fill
Live

The small square in the bottom-right corner of a selection — drag it down to fill cells. Works exactly like Excel. Each filled cell goes through valueParser, so validation still applies.

Pro tip

Select a range of cells before dragging the fill handle to populate multiple columns at once. The fill handle uses requestAnimationFrame internally, so it stays smooth even on large selections.

Undo / Redo

The useUndoRedo hook (React) tracks everything: inline edits, paste, fill, and delete. Ctrl+Z / Ctrl+Y work out of the box, and you can wire up buttons too:

const { handleCellValueChanged, undo, redo, canUndo, canRedo } = useUndoRedo({
data,
setData,
getRowId: (item) => item.id,
});

Pass handleCellValueChanged as onCellValueChanged — it intercepts changes and pushes them onto the undo stack before updating your state. Undo/redo are also available in the right-click context menu.

Props reference

PropTypeDescription
editablebooleanEnable editing grid-wide (on OGrid)
editableboolean | (item: T) => booleanPer-column/row editability (on IColumnDef)
cellEditor'text' | 'select' | 'checkbox' | 'richSelect' | ComponentTypeEditor to use
cellEditorPopupbooleanRender editor in a floating popover
cellEditorParamsCellEditorParamsEditor parameters (e.g., { values: [...] })
valueParser(params) => unknownValidate/transform on edit, paste, fill, delete. Return undefined to reject.
valueFormatter(value, item) => stringFormat value for display and clipboard copy
onCellValueChanged(event) => voidFires after any cell value change
onUndo / onRedo() => voidUndo/redo callbacks
canUndo / canRedobooleanEnable/disable undo/redo in context menu

Next steps