Skip to main content

Search rules

Search all UI Guides rules by name, category, or source

interactionsARIA

One Tab Stop per Composite Widget

MUST

MUST: A composite widget (toolbar, listbox, menu, radiogroup, tablist, grid, tree) is ONE tab stop: roving `tabindex` — exactly one child at `0`, every other child at `-1` — with arrow keys moving the `0` and calling `.focus()`, and Tab moving past the whole widget. Twelve children must not be twelve tab stops. (Distinct from positive `tabindex`: here every value is `0` or `-1`; the bug is how many are `0`.)

Give a toolbar, listbox or grid a single tab stop and move within it using the arrow keys

<button tabIndex={i === activeIndex ? 0 : -1} onKeyDown={onArrows} />
// ArrowRight: setActiveIndex(i + 1); itemRefs[i + 1].current.focus();

Bad

Good

Why it matters

Tab crosses widgets; arrow keys move within one. A toolbar, listbox, menu, radiogroup, tablist, grid or tree is ONE thing in the tab sequence, no matter how many children it has — that is what makes it a composite. Get this wrong and a twelve-item filter bar becomes twelve stops the user must Tab past to reach the form below, and the arrow keys, which is what a screen reader user will actually press inside a listbox, do nothing at all.

There are two implementations, and this one is roving tabindex: exactly one child carries tabindex="0" while every other child carries tabindex="-1", so the browser can only Tab into the active child; your arrow-key handler then moves the 0 to the next child and calls .focus() on it. Because real DOM focus moves, the browser scrolls the active child into view and paints the focus ring for free.

The alternative, aria-activedescendant, is a separate principle — reach for it only when focus must stay in a text input. Note this is not the same complaint as "Never Use Positive tabindex", which is about tabindex="1" and up wrecking the global document order; here every value is 0 or -1 and the bug is how many of them are 0.

Built by Gleb Stroganov, design engineer at Evil Martians.

The rules come from other people's skills and guidelines — Vercel, Rauno Freiberg, @Ibelick, impeccable, Emil Kowalski, Tailwind, RAMS — each one credited on the Sources page. The work here is extraction and wiring: every rule is pulled into one corpus, given a good and a bad example you can operate, a MUST/SHOULD/NEVER rule an agent can paste, and a link back to where it came from.