CodePark/ questions / Focus management
START PRACTICINGPRACTICE
Frontend question · Accessibility › Focus management

What is a roving tabindex? When would you use aria-activedescendant instead?

function onKeyDown(e: KeyboardEvent, items: HTMLElement[], i: number): void {
  let next = -1;
  if (e.key === "ArrowRight") next = (i + 1) % items.length;
  if (e.key === "ArrowLeft") next = (i - 1 + items.length) % items.length;
  if (next < 0) return;
  items[i].tabIndex = -1;
  items[next].tabIndex = 0;
  items[next].focus();
}

A roving tabindex is a way to make a group of items count as one Tab stop. Inside the group, the user moves with the arrow keys.

How it works:

  • Exactly one item has tabindex="0". All the others have tabindex="-1".
  • Tab enters the group on the item with 0, and the next Tab leaves the group.
  • An arrow key moves 0 to the next item, sets the old one to -1, and calls focus() on the new one.
  • When the user comes back later, focus returns to the last item they used.

Example: a toolbar with 10 buttons, or a row of tabs. Without this, the user presses Tab 10 times to get past the toolbar.

aria-activedescendant is the other way. Real focus stays on one element, usually an input. That element's aria-activedescendant points to the id of the "virtually focused" option. The screen reader announces that option. Use it when focus must stay in a text box while the user arrows through options, as in a combobox (an autocomplete input with a list). The user can keep typing.

In short: roving tabindex moves real focus. aria-activedescendant keeps focus still and points at the active item.

  • A roving tabindex makes a group of items a single Tab stop.
  • One item has tabindex="0" and the rest have tabindex="-1".
  • Arrow keys move the 0 and call focus() on the new item.
  • aria-activedescendant keeps real focus on one element and points to the active option.
  • Use aria-activedescendant when focus must stay in an input, like a combobox.