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
| Name | Type and behavior |
|---|---|
options | UseGridFocusOptions · requiredConfiguration object for grid focus behavior. |
options.columns | number · requiredNumber of columns in the grid. Used for up/down navigation (moves by this many cells). |
options.cellSelector | string · 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 · optionalPredicate for whether a matched cell can receive focus. Omit to treat every matched cell as focusable. |
options.getFocusTarget | (cell: HTMLElement) => HTMLElement | null · optionalResolves 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 · optionalCallback 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 · optionalCallback when navigation would go after the last cell. Receives the column index and offset. |
options.onPageUp | () => void · optionalCallback for Page Up key (e.g., navigate to previous month in calendars). |
options.onPageDown | () => void · optionalCallback for Page Down key (e.g., navigate to next month in calendars). |
options.hasRovingTabIndex | boolean · optional · default falseOwn 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
| Name | Type and behavior |
|---|---|
gridRef | React.RefObject<HTMLElement | null>Ref to attach to the grid container element. |
handleKeyDown | (e: React.KeyboardEvent) => voidKey down handler to attach to the grid container. |
handleFocus | (e: React.FocusEvent) => voidFocus 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) => voidFocus a specific cell by index (clamped to valid range). |
focusFirst | () => voidFocus the first focusable cell in the grid. |
focusLast | () => voidFocus 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.