Recipe
Tabs
An ARIA-compliant tab strip with arrow-key navigation in one capsule.
Markup
<div ui-tabs role="tablist">
<button ref="tab0" data-index="0" role="tab" aria-controls="p1">One</button>
<button ref="tab1" data-index="1" role="tab" aria-controls="p2">Two</button>
<button ref="tab2" data-index="2" role="tab" aria-controls="p3">Three</button>
<section ref="panel0" id="p1" role="tabpanel">...</section>
<section ref="panel1" id="p2" role="tabpanel" hidden>...</section>
<section ref="panel2" id="p3" role="tabpanel" hidden>...</section>
</div>Capsule
import { UI, Capsule } from "tiny-engine-core";
class Tabs extends Capsule {
constructor(el, options) {
super(el, options);
this.tabs = Array.from(this.el.querySelectorAll("[role=tab]"));
this.panels = Array.from(this.el.querySelectorAll("[role=tabpanel]"));
this.activate(0, false);
this.on(this.el, "click", (event) => {
const tab = event.target.closest("[role=tab]");
if (tab) this.activate(Number(tab.dataset.index));
});
this.on(this.el, "keydown", (event) => {
const current = this.tabs.indexOf(document.activeElement);
if (event.key === "ArrowRight") this.activate((current + 1) % this.tabs.length);
if (event.key === "ArrowLeft") this.activate((current - 1 + this.tabs.length) % this.tabs.length);
});
}
activate(index, focus = true) {
this.tabs.forEach((tab, i) => {
tab.setAttribute("aria-selected", String(i === index));
tab.tabIndex = i === index ? 0 : -1;
});
this.panels.forEach((panel, i) => panel.toggleAttribute("hidden", i !== index));
if (focus) this.tabs[index].focus();
}
}
UI.register("tabs", Tabs);
UI.init();