<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)
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.