Cell Editing

Learn the core cell-editing flow, from declaring editable cells and entering by pointer or keyboard to IME input, save, cancel, and movement.

#cell-editing#editable#editTrigger#keyboard#IME#text-editor
Last reviewed: 2026-08-21
GitHub
import * as React from 'react';
import { BGrid, type BGridCellAddress, type BGridColumn } from 'beautiful-grid';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
import {
  applyEditingDataChange,
  cloneEditingOrders,
  type EditingOrder,
  withEditingCellClasses,
} from './editing/shared';

export default function BasicEditingExample() {
  const [data, setData] = React.useState(cloneEditingOrders);
  const [activeCell, setActiveCell] = React.useState<BGridCellAddress>({ rowIndex: 0, columnIndex: 1 });
  const containerRef = React.useRef<HTMLDivElement>(null);
  const { width, height } = useContainerSize(containerRef);

  const columns = React.useMemo<BGridColumn<EditingOrder>[]>(
    () => withEditingCellClasses<EditingOrder>([
      { key: 'orderCode', label: '주문 코드', width: 150, editable: false },
      {
        key: 'customerName',
        label: '고객명 · 더블클릭',
        width: 190,
        editable: true,
        editor: { type: 'text', inputProps: { maxLength: 50, autoComplete: 'off' } },
      },
      {
        key: 'note',
        label: '메모 · 한 번 클릭',
        width: 210,
        editable: true,
        editTrigger: 'click',
        editor: { type: 'text', inputProps: { maxLength: 80, autoComplete: 'off' } },
      },
      { key: 'status', label: '상태 · 읽기 전용', width: 130, editable: false },
    ]),
    [],
  );

  return (
    <div className='flex min-h-0 flex-col gap-3'>
      <div className='rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm leading-6 text-slate-700'>
        <strong>마우스:</strong> 고객명은 Grid 기본값인 더블클릭, 메모는 컬럼의 <code>editTrigger='click'</code>으로
        편집합니다.
        <br />
        <strong>키보드:</strong> 방향키로 셀을 이동하고 바로 입력하면 기존 값을 대체합니다. <kbd>Enter</kbd> 또는{' '}
        <kbd>F2</kbd>는 기존 값을 유지하며 시작하고, <kbd>Tab</kbd>은 저장 후 이동, <kbd>Escape</kbd>는 취소합니다.
        <output aria-live='polite' className='mt-1 block font-mono text-xs text-blue-700'>
          활성 셀: 행 {activeCell.rowIndex + 1}, 열 {activeCell.columnIndex + 1}
        </output>
      </div>
      <DataGridContainer ref={containerRef} style={{ height: 340 }}>
        <BGrid<EditingOrder>
          width={width}
          height={height}
          data={data}
          columns={columns}
          rowKey='id'
          editable
          variant='vertical-bordered'
          editTrigger='dblclick'
          showLineNumber
          cellSelectionOptions={{ enabled: true }}
          cellNavigationOptions={{
            enabled: true,
            editOnEnter: true,
            activeCell,
            onActiveCellChange: cell => cell && setActiveCell(cell),
          }}
          onChangeData={(sourceIndex, _columnIndex, values, _column, meta) => {
            setData(current => applyEditingDataChange(current, sourceIndex, values, meta));
          }}
        />
      </DataGridContainer>
    </div>
  );
}

To enable cell editing, set editable on both the Grid and the target column, then define column.editor. This page covers the core flow from pointer activation through keyboard and IME input, saving, canceling, and navigation. Separate guides cover Select, lookup, and editing-event extensions.

1. Minimum setup

const columns: BGridColumn<Order>[] = [
  {
    key: 'customerName',
    label: 'Customer name',
    width: 180,
    editable: true,
    editor: { type: 'text' },
  },
];

<BGrid<Order>
  width={720}
  height={360}
  data={data}
  columns={columns}
  rowKey='id'
  editable
/>

itemRender controls how a cell appears when it is not being edited, while editor provides the input UI during editing. You do not need an editor merely to change display formatting.

2. Grid defaults and column overrides

The default edit trigger is a double-click. Change the Grid-wide default with editTrigger, then override individual columns that should open immediately, such as a Select editor.

<BGrid editTrigger='dblclick' {...props} />

const columns: BGridColumn<Order>[] = [
  { key: 'name', editable: true, editor: { type: 'text' } },
  {
    key: 'status',
    editable: true,
    editTrigger: 'click',
    editor: statusEditor,
  },
];

The resolution order is column.editTrigger → grid.editTrigger → 'dblclick'. There is no editTrigger: 'none'. Use editable: false to make a cell read-only. Put button actions that are independent of cell editing in editorIcon.onClick or itemRender.

3. Start editing with the pointer or keyboard

  • Cell click or double-click: opens the editor according to the configured editTrigger.
  • Direct character input: opens a text editor and replaces the existing value.
  • Enter or F2: opens the editor while preserving the existing value.
  • Icon click: opens the editor when editorIcon.onClick is not defined.

Cell focus and editor focus are separate states. Activate a cell first, then open its editor from the keyboard. When editing ends, the Grid returns focus to the active cell.

4. Keyboard behavior

Key Cell focused Editing
Character input Starts a text edit by replacing the existing value Normal text input
Enter / F2 Starts editing while preserving the existing value Enter saves
Tab / Shift+Tab Moves to the next/previous cell Saves, then moves to the next/previous cell
Escape Clears the selection range Cancels changes and returns to the same cell
Arrow keys Moves the active cell Uses the input control’s default behavior
Ctrl/Cmd+C, V Copies or pastes the selected range Uses the input control’s default behavior

The built-in text editor defaults startOnInput to true. Set startOnInput: false to prevent direct character input on a focused cell from opening the editor.

editor: {
  type: 'text',
  startOnInput: false,
}

5. IME and keyboard movement settings

During IME composition, such as Korean text input, the Grid does not save an incomplete string even if Enter or blur occurs first. It commits the final string only after composition ends. For external plugins, also verify the composition behavior of the UI component you use.

<BGrid
  cellNavigationOptions={{
    enabled: true,
    editOnEnter: true,
    wrap: false,
  }}
/>

To control the active cell or review Home/End and PageUp/PageDown behavior, continue to Cell Focus and Keyboard Navigation.

6. Update application state

After the Grid saves a change internally, it calls onChangeData. Its first argument is the source index in the original data, even when sorting or filtering is active.

onChangeData={(sourceIndex, _columnIndex, values, _column, meta) => {
  setData(current =>
    current.map((item, index) =>
      index === sourceIndex ? meta?.dataItem ?? { ...item, values } : item,
    ),
  );
}}

The actual row data always lives in BGridDataItem<T>.values. Saving meta.dataItem also preserves the editedColumnIds for directly edited columns and the changedKeys for changed data fields. Directly edited cells receive the bgrid-cell-edited style, while all cells that share a changed key receive bgrid-cell-value-changed.

7. Choose the next guide

Goal Next guide
Use text, Select, and Date editors Built-in Editors
Integrate Ant Design or another external UI library External Editor Plugins
Show dropdown or search icons in idle cells Editor Icons
Combine autocomplete input with a lookup modal Lookup Editor
Validate changes and update related cells Editing Events and Transactions
Edit merged cells across frozen boundaries Merged Cell Editing