react-datatable
Guides

Add keyboard navigation

Use this guide when users need to move through dense tables quickly without relying on the mouse.

Row states

Keyboard navigation in react-datatable is built around an active row.

The active row is where keyboard movement currently is; selection is which rows are included in bulk actions.

Media placeholder: Keyboard navigation states in a realistic table

Show one table state with an active row, selected rows, and an open preview so the differences are visible at a glance.

Use keyboard navigation when users need to:

  • move row by row with ArrowUp and ArrowDown
  • open the active row with Enter
  • toggle preview with Space
  • expand or collapse grouped headers from the keyboard

If the table only needs checkbox selection and occasional mouse clicks, you may not need this yet.

Enable keyboard navigation explicitly

Turn it on with the keyboardNavigation prop.

<Datatable
  tableKey="customers"
  data={rows}
  columns={columns}
  getRowId={(row) => row.id}
  keyboardNavigation={{
    enabled: true,
    autoFocus: true,
  }}
/>

autoFocus defaults to true when keyboard navigation is active, so only disable it when another control should keep initial focus.

[!NOTE] Screenshot placeholder: active row, open-row action, and preview toggle visible in a realistic table with grouped and ungrouped states.

Supported keyboard navigation

react-datatable already supports these keyboard behaviors:

  • ArrowUp and ArrowDown move the active row
  • Enter opens the active data row through rowActions.onOpenRow
  • Space toggles preview when preview is configured
  • Enter and Space toggle grouped headers when the active item is a group header
  • Esc closes preview before clearing other interaction state

Use rowActions.onOpenRow for the main keyboard open behavior.

<Datatable
  tableKey="customers"
  data={rows}
  columns={columns}
  getRowId={(row) => row.id}
  keyboardNavigation={{ enabled: true }}
  rowActions={{
    onOpenRow: ({ row, rowId, trigger }) => {
      openCustomer(rowId, { trigger })
    },
  }}
/>

When Enter fires on an active data row, the table calls onOpenRow with trigger: "keyboard".

That makes it easy to share one row-opening path across mouse and keyboard flows while still preserving analytics or UI branching when needed.

Wire Space to row preview when preview is available

Use preview when users need quick inspection without fully opening the row.

<Datatable
  tableKey="customers"
  data={rows}
  columns={columns}
  getRowId={(row) => row.id}
  keyboardNavigation={{ enabled: true }}
  rowActions={{
    onTogglePreviewRow: ({ rowId, nextOpen }) => {
      trackPreviewToggle(rowId, nextOpen)
    },
  }}
  preview={{
    floating: {
      draggable: true,
      storageKey: "customers-preview",
    },
    renderPreview: ({ row, close }) => <CustomerPreview customer={row} onClose={close} />,
  }}
/>

When preview is configured:

  • Space toggles preview for the active data row
  • Esc closes the preview first
  • the row action callback can observe whether the preview is opening or closing

The grid also enables keyboard navigation by default when preview rendering or onOpenRow is present, but it is still better to configure the behavior intentionally.

[!NOTE] Screenshot placeholder: active row with keyboard focus ring plus an open preview panel, showing the keyboard-driven inspection loop rather than only the resting table state.

Grouped rows change Enter and Space behavior

Grouped headers are keyboard-active items too.

When the active item is a group header instead of a data row:

  • Enter toggles group expansion
  • Space also toggles group expansion

That means grouped tables need a clear mental model:

  • data rows open or preview
  • group headers expand or collapse

If you mix grouping and keyboard navigation, test both grouped and ungrouped states instead of assuming row behavior carries over unchanged.

Be deliberate about focus ownership

The grid can claim focus on mount when keyboard navigation is enabled.

Keep autoFocus enabled when the table is the main work surface.

Disable it when:

  • the page opens into a search field or form
  • the table appears below a required input
  • automatic grid focus would interrupt a setup flow
<Datatable
  tableKey="customers"
  data={rows}
  columns={columns}
  getRowId={(row) => row.id}
  keyboardNavigation={{
    enabled: true,
    autoFocus: false,
  }}
/>

A table that steals focus at the wrong moment feels broken even when the keyboard logic itself is correct.

[!NOTE] Screenshot placeholder: a page layout where the table is below another input, illustrating when autoFocus: false protects the surrounding setup flow.

Verify keyboard navigation before you move on

Before you continue, confirm that:

  • ArrowUp and ArrowDown move the active row predictably
  • Enter opens active data rows through rowActions.onOpenRow
  • Space toggles preview when preview is configured
  • group headers expand and collapse from the keyboard
  • Esc closes preview before clearing other interaction state
  • active-row styling is visually distinct from checkbox selection
  • grid autofocus helps more than it hurts in the surrounding screen

On this page