A tab interface needs more than hiding one panel and showing another. Keep the selected tab, keyboard focus, ARIA state, and visible panel synchronized through one activation function. Use buttons so mouse, touch, and keyboard activation begin with native behavior.
Last updated: October 3, 2026.
<div role="tablist" aria-label="Account settings">
<button role="tab" id="tab-profile" aria-controls="panel-profile"
aria-selected="true" tabindex="0">Profile</button>
<button role="tab" id="tab-security" aria-controls="panel-security"
aria-selected="false" tabindex="-1">Security</button>
</div>
<section role="tabpanel" id="panel-profile" aria-labelledby="tab-profile">
Profile settings
</section>
<section role="tabpanel" id="panel-security" aria-labelledby="tab-security" hidden>
Security settings
</section>
<script>
const tabs = [...document.querySelectorAll('[role="tab"]')];
function activateTab(tab) {
for (const item of tabs) {
const selected = item === tab;
item.setAttribute('aria-selected', String(selected));
item.tabIndex = selected ? 0 : -1;
document.getElementById(item.getAttribute('aria-controls')).hidden = !selected;
}
}
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => activateTab(tab));
tab.addEventListener('keydown', (event) => {
if (!['ArrowLeft', 'ArrowRight'].includes(event.key)) return;
event.preventDefault();
const step = event.key === 'ArrowRight' ? 1 : -1;
const next = tabs[(index + step + tabs.length) % tabs.length];
next.focus();
activateTab(next);
});
});
</script>Each button controls exactly one panel. The activation function updates every related state in one pass, so repeated clicks do not leave multiple tabs selected or all panels hidden.
Follow the tab keyboard model
The W3C ARIA tabs pattern specifies tablist, tab, and tabpanel roles, along with aria-controls, aria-labelledby, and aria-selected. Only the active tab participates in the normal page tab sequence. Left and Right Arrow move within a horizontal tab list; Tab moves out to the next component.
This example activates a tab as focus moves with the arrow keys. If loading a panel is slow, move focus first and require Enter or Space to activate it instead.
Avoid fragile inline handlers
Do not repeat large onclick expressions in every tab. A shared activation function prevents one button from changing different classes, IDs, or attributes than the others. Keep IDs unique, and verify that every aria-controls value names an existing panel.
Test with mouse, Tab, both arrow keys, and a screen reader. Continue with accessible keyboard handling, HTML tab order, and JavaScript functions.