C6.22.3Roving tabindexdesignresearch

Roving tabindex keeps only one composite member in the Tab sequence; the rest move by arrows

Aliases: active tabindex · single-stop composite · roving tab index

What it is

Roving tabindex is the DOM technique for “Tab into the group, arrows inside”: at any moment only the current child's tabindex is 0 (or naturally focusable); the others are -1. Arrow keys move 0 to the next child, so the composite always occupies one stop in the Tab sequence. Without roving, every button is 0 and Tab shreds the toolbar into a chain—the convention can be right in interaction terms and still leak at implementation.

Why it happens

The browser's Tab walk looks at focusability and does not understand “these buttons belong to one toolbar.” Setting inactive children to -1 removes them from the list; arrow-key script then calls focus() and swaps tabindex. When focus leaves and returns, it should land on the last active child (remember which one holds 0) or the user loses place. Typical failures: 0 stuck on a deleted node after dynamic add/remove, or a re-render resetting every -1 back to 0. Roving fixes stop count. It does not fix focus visibility, nor whether reading order matches the picture.

Studying it

Use the accessibility tree or a Tab-probe script to count stops on the composite at rest and after moving internal focus; expect a constant 1. Then test leave-and-reenter restoration of the last child. Treat virtualized lists separately: destroying offscreen nodes easily drops the tabindex state machine. Automation can catch stop count; still walk with a keyboard to confirm focus() has not been sent to an item hidden by preventScroll.

Where it stops holding

Native <select> and some system menus already implement a single stop; do not wrap them in roving. Tab inside a contenteditable has “insert a tab character” semantics and must not turn editor cells into a roving grid. Touch taps do not go through tabindex. A composite with one child gains nothing. iframe and shadow-DOM boundaries cut script control of child tabindex and need their own instance inside the boundary.

Applying it

  • Toolbars, tab lists, grids: on mount, only the active child is 0, the rest -1; arrows update that pair and focus the new child.
  • On blur then refocus, restore the last active child rather than resetting to the first.
  • After children are added or removed, rebuild the active pointer so 0 is not left on an unmounted node.
  • Verify in an accessibility inspector: after several arrows on the toolbar, exactly one child should have tabindex 0, and one Tab should leave the whole group.

Related

  • Same group: C6.22.1 Composite widgets typically move among internals with arrow keys; Tab only enters and leaves · C6.22.2 Skip links let keyboard users bypass repeating navigation and reach main content · C6.22.4 Keyboard paths must be functionally equivalent to mouse-reachable paths, not visual presentation only
  • Adjacent: C6.05 Focus order · C6.03 Shortcut systems
  • Search: roving tabindex · tabindex · composite widget

Cards in the same group

Quick Actions

Share

Share this page

ios_share

https://hci.top/en/handbook/C6.22.3