<div class="tabs" data-tabs-container>
<ul class="tabs__list" role="tablist" aria-label="">
<li class="tabs__list-item" data-tabs-button-wrap>
<button class="tabs__button" role="tab" aria-selected="false" data-tabs-button>
Test Heading
</button>
</li>
<li class="tabs__list-item" data-tabs-button-wrap>
<button class="tabs__button" role="tab" aria-selected="false" data-tabs-button>
Test Heading II
</button>
</li>
<li class="tabs__list-item" data-tabs-button-wrap>
<button class="tabs__button" role="tab" aria-selected="false" data-tabs-button>
Test Heading III
</button>
</li>
</ul>
<div class="tabs__window">
<div class="tabs__panel" role="tabpanel" data-tabs-panel data-tabs-panel-active="false">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu ipsum.
Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque.</p>
</div>
<div class="tabs__panel" role="tabpanel" data-tabs-panel data-tabs-panel-active="false">
<p>Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu ipsum. Mauris viverra interdum mattis. Integer nec molestie
tellus. Phasellus nec semper nibh, eget sagittis neque.</p>
</div>
<div class="tabs__panel" role="tabpanel" data-tabs-panel data-tabs-panel-active="false">
<p>Phasellus nec semper nibh, eget sagittis neque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante
pulvinar dolor, sed rhoncus neque enim eu ipsum. Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque.</p>
</div>
</div>
</div>
<div class="tabs" data-tabs-container>
<ul class="tabs__list" role="tablist" aria-label="{{ heading }}">
{% for tab in tabs %}
<li class="tabs__list-item" data-tabs-button-wrap>
<button class="tabs__button" role="tab" aria-selected="false" data-tabs-button>
{{ tab.heading }}
</button>
</li>
{% endfor %}
</ul>
<div class="tabs__window">
{% for tab in tabs %}
<div class="tabs__panel" role="tabpanel" data-tabs-panel data-tabs-panel-active="false">
{{ tab.content }}
</div>
{% endfor %}
</div>
</div>
{
"tabs": [
{
"heading": "Test Heading",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu ipsum. Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque.</p>"
},
{
"heading": "Test Heading II",
"content": "<p>Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu ipsum. Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque.</p>"
},
{
"heading": "Test Heading III",
"content": "<p>Phasellus nec semper nibh, eget sagittis neque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu ipsum. Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque.</p>"
}
]
}
/*
* tab styles (tabs on desktop and mobile)
*
*****************************************************************************/
.tabs {
&__list {
margin: 0;
padding: 0;
font-size: 0;
}
&__list-item {
display: inline-block;
margin: 0;
list-style: none;
font-size: 0;
& + & {
border-left: 1px solid color(neutral, lighter);
}
}
&__button {
display: block;
border: 0;
border-bottom: 1px solid color(neutral, lighter);
padding: 10px 20px;
width: 100%;
background-color: color(neutral, lightest);
cursor: pointer;
appearance: none;
@media (min-width: $breakpoint-sm) {
border-top: 1px solid color(neutral, lighter);
}
&[aria-selected='true'] {
background-color: color(default, light);
@media (min-width: $breakpoint-sm) {
border-bottom: 0;
}
}
}
&__panel {
display: none;
&[data-tabs-panel-active='true'] {
display: block;
}
}
}
// Polyfills
if (window.Element && !Element.prototype.closest) {
Element.prototype.closest =
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i,
el = this;
do {
i = matches.length;
while (--i >= 0 && matches.item(i) !== el) {};
} while ((i < 0) && (el = el.parentElement));
return el;
};
}
if (!Array.prototype.forEach) {
Array.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
// Tabs Functionality
const tabs = document.querySelectorAll('[data-tabs-button]');
const panels = document.querySelectorAll('[data-tabs-panel]');
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener('click', (e) => {
const tabWrap = tabs[i].closest('[data-tabs-button-wrap]');
const panel = panels[i];
const panelSiblings = getSiblings(panel);
let tabSiblings = getSiblings(tabWrap);
tabs[i].setAttribute('aria-selected', 'true');
tabs[i].setAttribute('tabindex', '0');
panels[i].setAttribute('data-tabs-panel-active', 'true');
panels[i].setAttribute('tabindex', '0');
tabSiblings.forEach((sibling) => {
sibling.querySelector('[data-tabs-button]').setAttribute('aria-selected', 'false');
sibling.querySelector('[data-tabs-button]').setAttribute('tabindex', '-1');
});
panelSiblings.forEach((sibling) => {
sibling.setAttribute('data-tabs-panel-active', 'false');
sibling.setAttribute('tabindex', '-1');
});
});
// Arrow key controls
tabs[i].addEventListener('keydown', (e) => {
if (e.keyCode === 37) {
if (i === 0) {
tabs[tabs.length - 1].focus();
} else {
tabs[i - 1].focus();
}
}
if (e.keyCode === 39) {
if (i === tabs.length - 1) {
tabs[0].focus();
} else {
tabs[i + 1].focus();
}
}
});
}
// This should probably be extracted into a more
// global set of helper functions for pure JS
// engineers down the road. - Greg S.
function getSiblings(element) {
let siblings = [];
let sibling = element.parentNode.firstChild;
while (sibling) {
if (sibling.nodeType === 1 && sibling !== element) {
siblings.push(sibling);
}
sibling = sibling.nextSibling
}
return siblings;
};
if (tabs.length > 0) {
tabs[0].click();
}
Displays tabbed content with specific titles and descriptions grouped together. Only one tab can be visible at a time, with the first tab visible by default. This component functions as tabs on both desktop and mobile.
role="tablist"
.role="tab"
and is contained within the element with role="tablist"
.role="tabpanel"
.aria-controls
referring to its tabpanel.aria-selected="true"
and all other tab elements have aria-selected="false"
.tab-index="0"
and all other tab elements have tab-index="-1"
.tabpanel
has the property aria-labelledby
referring to its associated tab.:focus
state.aria-selected="true"
state.Space
or Enter
key activates the tab, which displays the associated panel.Tab
moves focus to the corresponding tabpanel element, which is next in the tab sequence.Shift + Tab
will move focus back to the activating tab.←
→
= Cycles tab focus. If focus is on the last tab, moves focus to the first tab. If focus is on the first tab, moves focus to the last tab.↑
↓
= Cycles tab focus (vertical tabs). If focus is on the last tab, moves focus to the first tab. If focus is on the first tab, moves focus to the last tab.Home
= Focus first tabEnd
= Focus last tab