<div class="tabs" data-tabs>
<div class="tabs__list" data-list>
<button class="tabs__button" data-target="tab1">Tab 1</button>
<button class="tabs__button" data-target="tab2">Tab 2</button>
<button class="tabs__button" data-target="tab3">Tab 3</button>
</div>
<div class="tabs__container" data-tab="tab1">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Cumque eum pariatur animi iusto quis qui impedit eligendi minima! Recusandae modi obcaecati illo ut, necessitatibus officiis nostrum eaque porro ea deserunt.</div>
<div class="tabs__container" data-tab="tab2">Inventore vero dolores deleniti eveniet quas aliquid! Vitae provident sequi molestias enim suscipit perspiciatis culpa excepturi nisi assumenda, impedit doloribus error quo autem rerum ad voluptatibus ratione sed maiores? Enim?</div>
<div class="tabs__container" data-tab="tab3">
<div class="tabs" data-tabs>
<div class="tabs__list" data-list>
<button class="tabs__button" data-target="nested-tab1">Nested tab 1</button>
<button class="tabs__button" data-target="nested-tab2">Nested tab 2</button>
</div>
<div class="tabs__container" data-tab="nested-tab1">Lorem ipsum dolor sit amet consectetur adipisicing elit. Minus placeat commodi, quas eos consequatur aperiam tempora, distinctio, debitis est quis obcaecati molestias temporibus pariatur iusto tenetur eligendi labore similique natus?</div>
<div class="tabs__container" data-tab="nested-tab2">Accusantium, voluptas eligendi? Odio expedita hic amet reiciendis. Iste praesentium eligendi eum aperiam fugit. Facere voluptatibus perspiciatis quaerat architecto hic omnis nulla. Accusantium quaerat id neque atque consequatur maiores inventore.</div>
</div>
</div>
</div>
body {
margin: 0;
padding: 16px;
font-size: 16px;
font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
}
.tabs {
border: 4px solid #5f84f3;
&__list {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
background: #b2c2f3;
}
&__button {
border: none;
padding: 8px 16px;
font-size: inherit;
font-family: inherit;
background: transparent;
&[aria-selected="true"] {
background: #5f84f3;
color: #ffffff;
}
}
&__container {
padding: 16px;
}
}
View Compiled
class Tabs {
constructor(root) {
this.root = root;
this.list = this.root.querySelector(':scope > [data-list]');
this.buttons = new Map([this.list.querySelectorAll(':scope > [data-target]')]
.map(entry => [
entry.dataset.target,
entry,
])
);
this.containers = new Map([this.root.querySelectorAll(':scope > [data-tab]')]
.map(entry => [entry.dataset.tab, entry])
);
this.salt = Math.random().toString(36).slice(2);
this.current = null;
this.active = null;
}
select(name) {
const keys = [this.buttons.keys()];
for (let [key, button] of this.buttons.entries()) {
button.setAttribute('aria-selected', key === name);
}
for (let [key, container] of this.containers.entries()) {
if (key === name) {
container.removeAttribute('hidden');
} else {
container.setAttribute('hidden', true);
}
}
this.active = keys.indexOf(name);
}
init() {
const keys = [this.buttons.keys()];
this.list.setAttribute('role', 'tablist');
this.list.addEventListener('keydown', event => {
if (event.code === 'Home') {
event.preventDefault();
this.buttons.get(keys[0]).focus();
}
if (event.code === 'End') {
event.preventDefault();
this.buttons.get(keys[keys.length - 1]).focus();
}
if (event.code === 'ArrowLeft') {
event.preventDefault();
this.buttons.get(keys[Math.max(0, this.current - 1)]).focus();
}
if (event.code === 'ArrowRight') {
event.preventDefault();
this.buttons.get(keys[Math.min(keys.length - 1, this.current + 1)]).focus();
}
});
for (let [key, button] of this.buttons.entries()) {
button.setAttribute('tabindex', '0');
button.setAttribute('id', `button_${this.salt}_${key}`);
button.setAttribute('role', 'tab');
button.setAttribute('aria-controls', `container_${this.salt}_${key}`);
button.addEventListener('click', event => {
event.preventDefault();
this.select(key);
});
button.addEventListener('focus', event => {
this.current = keys.indexOf(key);
});
button.addEventListener('keypress', event => {
if (event.code === 'Enter' || event.code === 'Space') {
event.preventDefault();
this.select(key);
}
});
}
for (let [key, container] of this.containers.entries()) {
container.setAttribute('id', `container_${this.salt}_${key}`);
container.setAttribute('role', 'tabpanel');
container.setAttribute('aria-labelledby', `button_${this.salt}_${key}`);
}
this.select(keys[0]);
}
static create(element) {
const instance = new Tabs(element);
instance.init();
return instance;
}
}
const containers = document.querySelectorAll('[data-tabs]');
for (let container of containers) {
const tabs = Tabs.create(container);
console.log(tabs)
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.