Jump to Categories

Accessibile tabs

<div class="tabs">
  
    <ul class="tabs__list" role="tablist">
        <li class="tabs__item active" role="tab" aria-selected="true" aria-controls="A" tabindex="0">Perceivable</li>
        <li class="tabs__item" role="tab" aria-selected="false" aria-controls="B" tabindex="-1">Operable</li>
        <li class="tabs__item" role="tab" aria-selected="false" aria-controls="C" tabindex="-1">Understandable</li>
        <li class="tabs__item" role="tab" aria-selected="false" aria-controls="D" tabindex="-1">Robust</li>
    </ul>
  
    <div class="tabs__group">
        <div data-panel="A" class="tabs__panel active" role="tabpanel" aria-hidden="false">A</div>
        <div data-panel="B" class="tabs__panel inactive" role="tabpanel" aria-hidden="true">B</div>
        <div data-panel="C" class="tabs__panel inactive" role="tabpanel" aria-hidden="true">C</div>
        <div data-panel="D" class="tabs__panel inactive" role="tabpanel" aria-hidden="true">D</div>
    </div>
  
</div>
const tabs = Array.from(document.querySelectorAll('.tabs__list > li'));

tabs.forEach(tab => tab.addEventListener('keydown', (ev) => {
  ev.preventDefault();
  openTabKey(tab, ev);
}));

tabs.forEach(tab => tab.addEventListener('click', (ev) => {
  ev.preventDefault();
  openTab(tab);
}));

const resetTabs = function (el) {
    const tabs = el.getElementsByClassName('tabs__item');
    for (let i = 0; i < tabs.length; i++) {
        tabs[i].classList.remove('active');
        tabs[i].setAttribute('aria-selected', 'false');
        tabs[i].tabIndex = '-1';
    }
    const tabBox = el.getElementsByClassName('tabs__panel');
    for (let i = 0; i < tabBox.length; i++) {
        tabBox[i].classList.remove('active');
        tabBox[i].setAttribute('aria-hidden', 'true');
    }
};

const openTab = function (tab) {
    resetTabs(tab.parentElement.parentElement);
    tab.classList.add('active');
    tab.setAttribute('aria-selected', 'true');
    tab.tabIndex = '0';

    const panel = tab.getAttribute('aria-controls');

    tab.parentElement.nextElementSibling.querySelectorAll(`[data-panel='${panel}']`)[0].classList.add('active');
    tab.parentElement.nextElementSibling.querySelectorAll(`[data-panel='${panel}']`)[0].setAttribute('aria-hidden', 'false');

  tab.focus();
};

const openTabKey = function (tab, e) {
    const key = e.which;
    if (key === 37 || key === 38 || (key === 33 && key.ctrlKey)) {
        const prevEl = tab.previousElementSibling;
        if (prevEl) {
            openTab(prevEl);
            e.preventDefault();
        } else {
            const lastItem = tab.parentElement.lastElementChild;
            openTab(lastItem);
            e.preventDefault();
        }
    } else if (key === 39 || key === 40 || (key === 34 && key.ctrlKey)) {
        const nextEl = tab.nextElementSibling;
        if (nextEl) {
            openTab(nextEl);
            e.preventDefault();
        } else {
            const firstItem = tab.parentElement.firstElementChild;
            openTab(firstItem);
            e.preventDefault();
        }
    }
};
.tabs {
  background-color: #eee;
  border: 1px solid #ccc;
  border-radius: 10px;
  padding: 20px;
}

.tabs__list {
  list-type: none;
  margin: 0 0 0 20px;
  padding: 0;
}
.tabs__list li {
  background: #fff;
  border: 1px solid #d9d9d9;
  border-bottom: none;
  border-radius: 10px 10px 0 0;
  float: left;
  list-style-type: none;
  margin: 0 5px 0 0;
  padding: 6px 9px 3px 9px;
  position: relative;
  top: 1px;
}

.tabs__item:hover {
  cursor:pointer;
}

.tabs__item:focus {
  outline: 2px solid #8cc63f;
  outline-offset: 2px;
}

.tabs__item.active {
  font-weight: bold;
  padding: 7px 9px 4px 9px;
  position: relative;
  top: 1px;
}
.tabs__group {
  background: #fff;
  border: 1px solid #CCC;
  border-radius: 10px;
  box-shadow: #e8e8e8 0 0px 3px;
  clear: both;
  padding: 30px;
  z-index: 99;
}

.tabs__panel {
  display: none;
}

.tabs__panel.active {
  display: block;
}

See the Pen OEmpMr by Shane Prendergast (@webknit) on CodePen.

Refs