useRovingTabIndex

Manages keyboard focus among items in an ARIA tree.

useRovingTabIndex implements the roving tabindex pattern for custom tree views. Only one tree item is in the page's Tab sequence. After focus enters the tree, users can move between visible tree items with arrow keys without tabbing through every item.

Use this hook when you are building a custom hierarchical tree whose items use role="treeitem". For general focus movement in toolbars, menus, and other composite widgets, use useFocusZone instead.

Example

The hook manages focus while the tree item handles expansion and selection.

Use Tab to enter the tree, then use the arrow keys, Home, and End to move focus.

  • src
    • components
      • Button.tsx
      • Dialog.tsx
  • README.md

Usage

Create a ref for the element with role="tree", pass it to the hook, and attach it to the tree container. Each participating item must have role="treeitem" and be programmatically focusable with tabIndex={-1}.

const containerRef = React.useRef<HTMLUListElement>(null)

useRovingTabIndex({
  containerRef: containerRef as React.RefObject<HTMLElement>,
})

return (
  <ul ref={containerRef} role="tree" aria-label="Files">
    <li role="treeitem" tabIndex={-1}>
      README.md
    </li>
  </ul>
)

No provider or hooks context is required. The hook sets up focus management on containerRef as a side effect and does not return a value.

When the tree's structure changes, pass values that should reinitialize focus management in the optional dependency list:

useRovingTabIndex(
  {
    containerRef: containerRef as React.RefObject<HTMLElement>,
  },
  [items],
)

Initial focus

When focus enters the tree, the hook focuses:

  1. The item with aria-current, if present.
  2. The currently focused tree item, if focus is already within the tree.
  3. The first tree item.

If pointer event handling would conflict with this behavior, track whether the pointer is down in a ref and pass it as mouseDownRef. While that value is true, the browser's normal pointer focus behavior takes precedence.

Keyboard behavior

The hook follows the WAI-ARIA Tree View pattern:

  • Arrow Down and Arrow Up move to the next or previous visible tree item.
  • Arrow Right moves from an expanded item to its first child.
  • Arrow Left moves from a collapsed item or end item to its parent.
  • Home and End move to the first or last visible item.
  • Page Up and Page Down move approximately one visible page.
  • Backspace moves to the parent item.

The hook does not expand or collapse nodes. Your tree item keyboard handler must update aria-expanded when Arrow Right is pressed on a collapsed item or Arrow Left is pressed on an expanded item. It must also implement activation and selection behavior.

Accessibility

  • Give the tree an accessible name with aria-label or aria-labelledby.
  • Use role="group" for a nested set of tree items.
  • Set aria-expanded on parent items and keep collapsed descendants hidden.
  • Keep focus and selection separate. Use aria-selected for selection, or aria-current when an item represents the current page or location.
  • Make every tree item programmatically focusable. Do not add every item to the page's Tab sequence.
  • Preserve the expected keyboard behavior. Do not repurpose the arrow keys for unrelated actions.
  • The hook manages focus only. It does not add roles, labels, expansion state, selection state, or announcements.

Nested controls that are not tree items keep their own focus behavior. Avoid adding interactive controls inside a tree item unless the interaction and keyboard behavior are clear and tested with assistive technology.

API

Loading data for useRovingTabIndex...