SnippetJavaScript
Tabs
WAI-ARIA tabs pattern: roles, keyboard arrows, and roving tabindex.
Explanation
Tabs need roles tablist/tab/tabpanel, aria-selected, keyboard support (Left/Right/Home/End), and only one tab in tab order at a time.
javascript
export function createTabs(root) {
const tabs = [...root.querySelectorAll('[role="tab"]')];
const panels = [...root.querySelectorAll('[role="tabpanel"]')];
function select(index) {
tabs.forEach((tab, i) => {
const on = i === index;
tab.setAttribute('aria-selected', String(on));
tab.tabIndex = on ? 0 : -1;
panels[i].hidden = !on;
});
tabs[index].focus();
}
tabs.forEach((tab, i) => {
tab.addEventListener('click', () => select(i));
tab.addEventListener('keydown', (e) => {
const key = e.key;
let next = i;
if (key === 'ArrowRight') next = (i + 1) % tabs.length;
else if (key === 'ArrowLeft') next = (i - 1 + tabs.length) % tabs.length;
else if (key === 'Home') next = 0;
else if (key === 'End') next = tabs.length - 1;
else return;
e.preventDefault();
select(next);
});
});
select(Math.max(0, tabs.findIndex((t) => t.getAttribute('aria-selected') === 'true')));
return { select };
}Usage example
html
<div data-tabs>
<div role="tablist" aria-label="Demo">
<button role="tab" aria-selected="true" aria-controls="p1" id="t1">One</button>
<button role="tab" aria-selected="false" aria-controls="p2" id="t2">Two</button>
</div>
<div role="tabpanel" id="p1" aria-labelledby="t1">Panel 1</div>
<div role="tabpanel" id="p2" aria-labelledby="t2" hidden>Panel 2</div>
</div>
<script>createTabs(document.querySelector('[data-tabs]'));</script>