redundant-focusable-children
Web validation
tabindex must include only one interactive child with tabindex="0" in the tab sequence.Composite widgets such as tablist, menu, radiogroup, toolbar, and grid group several related controls. They use the roving tabindex pattern: the Tab key moves into or out of the whole widget, and the arrow keys move between the items inside it.
For this to work, only the currently active item should have tabindex="0", and the rest should have tabindex="-1". When more than one item has tabindex="0", the widget adds extra Tab stops and behaves differently from what keyboard and assistive technology users expect.
Note: Give the inactive items tabindex="-1", not just no tabindex. Native controls such as <button> are focusable by default, so without tabindex="-1" they stay in the tab sequence. Custom items such as <div role="tab"> are not focusable at all without a tabindex, and tabindex="-1" is what allows the script to move focus to them with the arrow keys.
A few related cases work differently:
- Native radio groups already behave this way. Radio buttons that share the same
nameform a single tab stop, and the arrow keys move between them, so they don't need rovingtabindex. - Widgets that use
aria-activedescendantkeep focus on the container, which is the only element in the tab sequence. None of their items should havetabindex="0". Mixing the two methods is covered by the related conflicting-focus-management-methods validation.
Pass Patterns
Roving tabindex with a single active control
Only the focused control is in the tab sequence. The arrow key handler moves focus within the widget and updates which control has tabindex="0".
<div role="toolbar" aria-label="Text formatting" id="format-toolbar">
<button type="button" tabindex="0">Bold</button>
<button type="button" tabindex="-1">Italic</button>
<button type="button" tabindex="-1">Underline</button>
</div>
<script>
const toolbar = document.querySelector('#format-toolbar');
const buttons = Array.from(toolbar.querySelectorAll('button'));
toolbar.addEventListener('keydown', event => {
if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return;
event.preventDefault();
const currentIndex = buttons.indexOf(document.activeElement);
const nextIndex = event.key === 'ArrowRight'
? (currentIndex + 1) % buttons.length
: (currentIndex - 1 + buttons.length) % buttons.length;
buttons.forEach((button, index) => {
button.tabIndex = index === nextIndex ? 0 : -1;
});
buttons[nextIndex].focus();
});
</script>Fail Patterns
Every item left in the tab sequence
This tablist gives all three tabs tabindex="0", so the Tab key moves through each tab instead of leaving the widget after the active one.
<div role="tablist" aria-label="Account sections">
<button role="tab" aria-selected="true" tabindex="0">Profile</button>
<button role="tab" aria-selected="false" tabindex="0">Security</button>
<button role="tab" aria-selected="false" tabindex="0">Billing</button>
</div>Try It Yourself
Using a keyboard
- Place keyboard focus on the interactive element just before the composite widget.
- Press Tab once to enter the widget.
- Press Tab again.
- Use the arrow keys to check whether they move between child items.
Expected results: Tab lands on one child item, and the next Tab press leaves the widget. The arrow keys move between child items inside the widget.
Actual results: Tab moves to another child item in the same widget before leaving it.
Inspecting the browser dev-tools
- Inspect the composite widget in browser developer tools.
- Find the interactive child items that belong to the widget.
- Count how many of them have
tabindex="0".
Expected results: Only one child item has tabindex="0". The other child items in the same roving tabindex group have tabindex="-1".
Actual results: Two or more child items in the same widget have tabindex="0".
FAQ
Why is this validation considered a best practice?
No WCAG success criterion requires the roving tabindex pattern. A widget whose items are all in the tab sequence can still be operated with the keyboard, and its focus order can still be logical. However, the pattern is the keyboard convention described in the ARIA Authoring Practices Guide, and users of composite widgets expect Tab to move past the whole widget and the arrow keys to move within it.
Why is this issue marked as Moderate?
This issue adds extra tab stops and can make a composite widget harder to operate. It usually does not stop users from reaching the items, but it slows keyboard users down, creates unexpected movement, and makes the widget less predictable for keyboard and assistive technology users.
Standards and Regulations
Personas impacted by this issue
Learn More
- WAI-ARIA Authoring Practices: Developing a Keyboard Interface (roving tabindex)
- WAI-ARIA Authoring Practices: Tabs Pattern
- WAI-ARIA Authoring Practices: Toolbar Pattern