TrademarkTrademarkDocs
Ctrl k
Search docs...
Components

ArrowNavigation

Updated: August 31, 2026
View on GitHub
ArrowNavigation wraps a set of items and moves focus between them with the arrow keys. It is the component form of useArrowNavigation: the hook when you own the container and its key handler, this when you would rather not write either. Arrow keys are the expected way to move within a composite widget — a menu, a listbox, a toolbar, an emoji grid — where Tab should reach the widget once and then hand over. Select, DropdownWrapper, DatePicker and Kbar are built on it. layout decides which keys move focus:
  • "row" — Left and Right.
  • "column" — Up and Down.
  • "grid" — all four, with columns giving the row width so Up and Down step a whole row at a time.
wrap (on by default) carries focus from the last item back to the first. Turn it off for a list where hitting the end should feel like an end. Items are found with [role="option"], [data-value], button, [tabindex]:not([tabindex="-1"]). Override itemSelector when your items are none of those, and keep itemCount in step with what the selector actually matches — the count drives wrapping and the bounds, so a mismatch shows up as arrow keys that stop one item early. Set trapFocus to wrap the region in a FocusTrap. The two divide the keyboard cleanly: the trap owns Tab and Escape, ArrowNavigation owns the arrows. useArrowNavigationContext() returns the navigation state from inside the subtree, for an item that needs to know it is the focused one — to paint a highlight, or to scroll itself into view. Syncing setFocusedIndex from hover is worth doing: it keeps the keyboard highlight and the pointer from disagreeing about which item is current. initialFocusedIndex says which item is current, not that focus should move there. On mount the component takes focus only when you passed autoFocus, or when nothing else holds it — which is the case right after a keyed remount, where the element that had focus has just been removed and focusing it again is a restore. Every later move follows a keystroke, so it always takes focus. Without that rule a list remounting beside a control the user is already on would pull focus off it. A DatePicker does exactly that: it remounts its calendar whenever the month or year selector closes, and focus used to land on a day button instead of staying on the trigger the user had just used. When autoFocus does take focus and you have not named an initialFocusedIndex, it goes to the item marked aria-selected="true", the way a native select opens on the value it is already showing. A list with no selection — a menu, a command palette — starts at its first enabled item, as before. A selected item that is disabled is skipped: focus has to land somewhere usable. aria-selected is yours to author; Option sets it from its selected prop. Do not drive it from the focused index — that is what data-highlighted and the highlighted class are for. Conflating the two makes a screen reader announce whichever item the arrow keys have reached as the selected one, and leaves this with nothing to open on.
Prop
Type
Default
layout
Which arrow keys move focus. Required.
rowcolumngrid
—
itemCount
How many items the selector matches. Required.
number—
columns
Row width for the grid layout.
number8
onSelect
Called on Enter or Space.
(index: number) => void—
onFocusChange
Called whenever focus moves.
(index: number) => void—
wrap
Carry focus from the last item to the first.
booleantrue
initialFocusedIndex
Which item starts current. -1 means none; see above for when it takes focus.
number-1
itemSelector
How navigable children are found.
stringrole=option, data-value, button, tabindex
autoFocus
Take focus on mount — at initialFocusedIndex if set, else the selected item.
booleanfalse
disabled
Stop handling keys.
booleanfalse
disableHighlighting
Skip applying the highlighted state to items.
booleanfalse
trapFocus
Wrap the region in a FocusTrap.
booleanfalse
focusTrapActive
Whether that trap is active.
booleantrue
onEscape
Escape handler, passed to the trap.
() => void—
autoFocusTrap
Let the trap move focus in when it activates.
booleantrue
restoreFocus
Let the trap return focus when it deactivates.
booleantrue
role
ARIA role for the container, e.g. listbox or menu.
string—
aria-label
string—
children
React.ReactNode—
className
string—
style
React.CSSProperties—
Edit this page on GitHub
TrademarkTrademark
Built with curiosity by Lorant One and the Design Engineers Club