Skip to main content
React Data Grid provides comprehensive keyboard navigation support, allowing users to efficiently navigate cells, edit data, and perform actions without using a mouse.

Basic Navigation

Arrow Keys

Navigate between cells using arrow keys:
import { DataGrid, type Column } from 'react-data-grid';

const columns: Column<Row>[] = [
  { key: 'id', name: 'ID' },
  { key: 'name', name: 'Name' },
  { key: 'email', name: 'Email' }
];

function MyGrid() {
  return <DataGrid columns={columns} rows={rows} />;
}
  • ↑ Up: Move to cell above
  • ↓ Down: Move to cell below
  • ← Left: Move to cell on the left
  • → Right: Move to cell on the right
The grid automatically scrolls to keep the selected cell visible during navigation.

Editing

Enter Edit Mode

Start editing cells using keyboard:
import { renderTextEditor } from 'react-data-grid';

const columns: Column<Row>[] = [
  {
    key: 'name',
    name: 'Name',
    renderEditCell: renderTextEditor
  }
];
Press Enter to start editing the selected cell.
// Default behavior
// Enter key opens editor on editable cells

While Editing

  • Enter: Save changes and close editor
  • Tab: Save changes and move to next cell
  • Click Outside: Save changes (default behavior)

Row Selection

Select rows using keyboard:
import { useState } from 'react';
import { DataGrid, SelectColumn } from 'react-data-grid';

function MyGrid() {
  const [selectedRows, setSelectedRows] = useState<ReadonlySet<number>>(new Set());
  
  const columns = [
    SelectColumn,
    { key: 'name', name: 'Name' },
    { key: 'email', name: 'Email' }
  ];
  
  return (
    <DataGrid
      columns={columns}
      rows={rows}
      rowKeyGetter={(row) => row.id}
      selectedRows={selectedRows}
      onSelectedRowsChange={setSelectedRows}
    />
  );
}
Shift+Space: Toggle row selectionFrom src/DataGrid.tsx:
// Select the row on Shift + Space
if (isSelectable && shiftKey && key === ' ') {
  const rowKey = rowKeyGetter(row);
  selectRow({ row, checked: !selectedRows.has(rowKey), isShiftClick: false });
  event.preventDefault(); // prevent scrolling
}

TreeGrid Navigation

TreeDataGrid provides additional keyboard navigation:
import { useState } from 'react';
import { TreeDataGrid, type Column } from 'react-data-grid';

function MyTreeGrid() {
  const [expandedGroupIds, setExpandedGroupIds] = useState<ReadonlySet<unknown>>(new Set());
  
  return (
    <TreeDataGrid
      columns={columns}
      rows={rows}
      groupBy={['category']}
      rowGrouper={rowGrouper}
      expandedGroupIds={expandedGroupIds}
      onExpandedGroupIdsChange={setExpandedGroupIds}
    />
  );
}
When a group row is focused:
  • → Right Arrow: Expand collapsed group
  • ← Left Arrow: Collapse expanded group
From src/TreeDataGrid.tsx:
if (
  idx === -1 &&
  ((event.key === leftKey && row.isExpanded) ||
   (event.key === rightKey && !row.isExpanded))
) {
  event.preventDefault();
  event.preventGridDefault();
  toggleGroup(row.id);
}

Custom Keyboard Behavior

Prevent Default Actions

Customize keyboard behavior using onCellKeyDown:
import type { CellKeyDownArgs, CellKeyboardEvent } from 'react-data-grid';

function handleCellKeyDown(
  args: CellKeyDownArgs<Row>,
  event: CellKeyboardEvent
) {
  if (args.mode === 'SELECT' && event.key === 'Enter') {
    // Prevent entering edit mode on Enter
    event.preventGridDefault();
    
    // Custom action instead
    console.log('Custom action for', args.row);
  }
}

<DataGrid
  columns={columns}
  rows={rows}
  onCellKeyDown={handleCellKeyDown}
/>
Customize Tab key behavior:
function handleCellKeyDown(
  args: CellKeyDownArgs<Row>,
  event: CellKeyboardEvent
) {
  if (args.mode === 'SELECT' && event.key === 'Tab') {
    // Prevent default tab navigation
    event.preventGridDefault();
    
    // Custom logic: skip non-editable columns
    const currentIdx = args.column.idx;
    const nextEditableIdx = columns.findIndex(
      (col, idx) => idx > currentIdx && col.renderEditCell
    );
    
    if (nextEditableIdx !== -1) {
      args.selectCell({
        idx: nextEditableIdx,
        rowIdx: args.rowIdx
      });
    }
  }
}

Custom Edit Triggers

Control when cells enter edit mode:
function handleCellClick(
  args: CellMouseArgs<Row>,
  event: CellMouseEvent
) {
  // Enter edit mode on single click
  if (args.column.renderEditCell) {
    args.selectCell(true); // true = enable editor
  }
}

<DataGrid
  columns={columns}
  rows={rows}
  onCellClick={handleCellClick}
/>
Only allow editing under certain conditions:
function handleCellKeyDown(
  args: CellKeyDownArgs<Row>,
  event: CellKeyboardEvent
) {
  if (args.mode === 'SELECT' && event.key === 'Enter') {
    // Only allow editing if user has permission
    if (!userHasEditPermission(args.row)) {
      event.preventGridDefault();
      alert('You do not have permission to edit this row');
      return;
    }
    
    // Allow default behavior (enter edit mode)
  }
}

Focus Management

The grid manages focus automatically:
// From src/DataGrid.tsx
useLayoutEffect(() => {
  if (shouldFocusCell) {
    if (selectedPosition.idx === -1) {
      focusRow(gridRef.current!);
    } else {
      focusCell(gridRef.current!);
    }
    setShouldFocusCell(false);
  }
}, [shouldFocusCell, selectedPosition.idx, gridRef]);

Programmatic Focus

Control focus programmatically:
import { useRef } from 'react';
import type { DataGridHandle } from 'react-data-grid';

function MyComponent() {
  const gridRef = useRef<DataGridHandle>(null);
  
  function focusCell(rowIdx: number, colIdx: number) {
    gridRef.current?.selectCell({ rowIdx, idx: colIdx }, { shouldFocusCell: true });
  }
  
  return (
    <>
      <button onClick={() => focusCell(0, 0)}>Focus first cell</button>
      <DataGrid
        ref={gridRef}
        columns={columns}
        rows={rows}
      />
    </>
  );
}

RTL Navigation

In RTL mode, horizontal navigation is reversed:
<DataGrid
  columns={columns}
  rows={rows}
  direction="rtl"
/>
From src/DataGrid.tsx:
const { leftKey, rightKey } = getLeftRightKey(direction);

// In RTL mode:
// - leftKey is 'ArrowRight'
// - rightKey is 'ArrowLeft'
Arrow key behavior automatically adjusts:
  • → Right: Moves left in RTL
  • ← Left: Moves right in RTL

Accessibility

ARIA Support

The grid implements proper ARIA attributes:
<div
  role="grid"
  aria-label="Data grid"
  aria-colcount={columns.length}
  aria-rowcount={rows.length + headerRowsCount}
  aria-multiselectable={isSelectable}
  tabIndex={-1} // Grid container is focusable
>
  <div role="row" aria-rowindex={1}>
    <div role="columnheader" aria-colindex={1} tabIndex={-1}>
      Column 1
    </div>
  </div>
  <div role="row" aria-rowindex={2} aria-selected={false}>
    <div role="gridcell" aria-colindex={1} tabIndex={0}>
      Cell content
    </div>
  </div>
</div>

Screen Reader Support

  • Grid role: Announces as data grid
  • Aria attributes: Provide context about position and selection
  • Focus management: Maintains logical focus order
  • Selection state: Announces selected rows
Provide proper labels for accessibility:
<DataGrid
  aria-label="Employee directory"
  aria-description="Searchable list of all employees with contact information"
  columns={columns}
  rows={rows}
/>

// Or with aria-labelledby
<>
  <h2 id="grid-title">Employee Directory</h2>
  <DataGrid
    aria-labelledby="grid-title"
    columns={columns}
    rows={rows}
  />
</>

API Reference

DataGrid Props

onCellKeyDown

onCellKeyDown?: (args: CellKeyDownArgs<R, SR>, event: CellKeyboardEvent) => void
Description: Callback triggered when a key is pressed in a cell. Usage: Customize keyboard behavior or add custom shortcuts.

onSelectedCellChange

onSelectedCellChange?: (args: CellSelectArgs<R, SR>) => void
Description: Triggered when the selected cell changes.

CellKeyDownArgs

type CellKeyDownArgs<TRow, TSummaryRow = unknown> =
  | SelectCellKeyDownArgs<TRow, TSummaryRow>
  | EditCellKeyDownArgs<TRow, TSummaryRow>;

interface SelectCellKeyDownArgs<TRow, TSummaryRow> {
  mode: 'SELECT';
  column: CalculatedColumn<TRow, TSummaryRow> | undefined;
  row: TRow;
  rowIdx: number;
  selectCell: (position: Position, options?: SelectCellOptions) => void;
}

interface EditCellKeyDownArgs<TRow, TSummaryRow> {
  mode: 'EDIT';
  column: CalculatedColumn<TRow, TSummaryRow>;
  row: TRow;
  rowIdx: number;
  navigate: () => void;
  onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
}

CellKeyboardEvent

type CellKeyboardEvent = CellEvent<React.KeyboardEvent<HTMLDivElement>>;

interface CellEvent<E> extends E {
  preventGridDefault: () => void;
  isGridDefaultPrevented: () => boolean;
}
Methods:
  • preventGridDefault(): Prevent the grid’s default keyboard behavior
  • isGridDefaultPrevented(): Check if default was prevented

DataGridHandle

interface DataGridHandle {
  element: HTMLDivElement | null;
  scrollToCell: (position: PartialPosition) => void;
  selectCell: (position: Position, options?: SelectCellOptions) => void;
}

Build docs developers (and LLMs) love