EXEPERTAI LAB

Research alpha

Time Machine
EXEPERTAI LAB
GALLERY / COLLECTION
← Browse Astryx gallery
hooks·hook·@astryxdesign/core

useGridFocus

Manages keyboard navigation within a 2D grid following the WAI-ARIA grid pattern. Supports arrow keys for cell-to-cell navigation, Home/End for row boundaries, Ctrl+Home/Ctrl+End for grid boundaries, and Page Up/Down for custom callbacks (e.g., month navigation in calendars). Boundary navigation callbacks allow cross-grid navigation.

@astryxdesign/core/hooks

Signature

Call shape
useGridFocus<T extends HTMLElement = HTMLElement>(options: UseGridFocusOptions): UseGridFocusReturn<T>
Parameters
NameType and behavior
optionsUseGridFocusOptions · required
Configuration object for grid focus behavior.
options.columnsnumber · required
Number of columns in the grid. Used for up/down navigation (moves by this many cells).
options.cellSelectorstring · optional · default 'button:not([disabled]), [tabindex]:not([tabindex="-1"])'
Selector for cells within the grid. Should match ALL cell positions in DOM order (including disabled/empty) so grid geometry is preserved.
options.isCellFocusable(cell: HTMLElement) => boolean · optional
Predicate for whether a matched cell can receive focus. Omit to treat every matched cell as focusable.
options.getFocusTarget(cell: HTMLElement) => HTMLElement | null · optional
Resolves the element to focus for a cell, e.g. a button inside a role="gridcell" wrapper. Omit to focus the cell itself.
options.onNavigateBefore(column: number, offset: number) => void · optional
Callback when navigation would go before the first cell. Receives the column index and offset (1 for horizontal, columns for vertical).
options.onNavigateAfter(column: number, offset: number) => void · optional
Callback when navigation would go after the last cell. Receives the column index and offset.
options.onPageUp() => void · optional
Callback for Page Up key (e.g., navigate to previous month in calendars).
options.onPageDown() => void · optional
Callback for Page Down key (e.g., navigate to next month in calendars).
options.hasRovingTabIndexboolean · optional · default false
Own a single roving tab stop across the grid: one focusable cell (its resolved focus target) carries tabindex="0", the rest -1. Stamped/repaired on render and moved with arrow navigation. Attach the returned handleFocus to the container onFocus.
Returns
NameType and behavior
gridRefReact.RefObject<HTMLElement | null>
Ref to attach to the grid container element.
handleKeyDown(e: React.KeyboardEvent) => void
Key down handler to attach to the grid container.
handleFocus(e: React.FocusEvent) => void
Focus handler for the grid container. Keeps the roving tab stop in sync when hasRovingTabIndex is enabled; a no-op otherwise, so always safe to attach.
focusCell(index: number) => void
Focus a specific cell by index (clamped to valid range).
focusFirst() => void
Focus the first focusable cell in the grid.
focusLast() => void
Focus the last focusable cell in the grid.

Usage

Manages keyboard navigation within a 2D grid following the WAI-ARIA grid pattern. Supports arrow keys for cell-to-cell navigation, Home/End for row boundaries, Ctrl+Home/Ctrl+End for grid boundaries, and Page Up/Down for custom callbacks (e.g., month navigation in calendars). Boundary navigation callbacks allow cross-grid navigation.

  • Use for calendar date grids: wire onPageUp/onPageDown to month navigation and onNavigateBefore/onNavigateAfter for cross-month arrow key navigation.
  • Attach both gridRef and handleKeyDown to the grid container element.
  • For roving-tabindex grids (e.g. Calendar), set hasRovingTabIndex: true and attach handleFocus to the container onFocus; seed one focus target with tabindex=0 and the hook repairs and moves it.
  • Use for simple linear lists; prefer useListFocus for 1D navigation.