
Implementing the WAI-ARIA Tabs Pattern with Roving Tabindex
Tabbed interfaces are common components in settings views, documentation, and dashboards. However, many tab implementations make the mistake of using standard Tab key navigation between tab buttons, violating the WAI-ARIA authoring practices.
Under ARIA conventions, pressing the Tab key should move focus into the active tab's content panel, while the Arrow keys (ArrowLeft, ArrowRight, Home, End) must navigate between the tab headers using a Roving tabindex.
Example
<div class="tabs-component">
<div role="tablist" aria-label="Account Settings">
<button role="tab" id="tab-profile" aria-selected="true" aria-controls="panel-profile" tabindex="0">Profile</button>
<button role="tab" id="tab-security" aria-selected="false" aria-controls="panel-security" tabindex="-1">Security</button>
</div>
<div role="tabpanel" id="panel-profile" aria-labelledby="tab-profile" tabindex="0">
<h3>Profile Information</h3>
</div>
<div role="tabpanel" id="panel-security" aria-labelledby="tab-security" tabindex="0" hidden>
<h3>Security Credentials</h3>
</div>
</div>
Implementation
const tablist = document.querySelector('[role="tablist"]');
const tabs = tablist.querySelectorAll('[role="tab"]');
tablist.addEventListener('keydown', (e) => {
const currentTab = document.activeElement;
let targetTab = null;
if (e.key === 'ArrowRight') {
targetTab = currentTab.nextElementSibling || tabs[0];
} else if (e.key === 'ArrowLeft') {
targetTab = currentTab.previousElementSibling || tabs[tabs.length - 1];
}
if (targetTab) {
currentTab.setAttribute('tabindex', '-1');
targetTab.setAttribute('tabindex', '0');
targetTab.focus();
targetTab.click();
}
});
Main Point: The WAI-ARIA Tabs pattern requires Arrow keys to navigate between tabs and the Tab key to jump directly into the active panel.
Credits
Photo by Dan Freeman on Unsplash