<div class="tabs-to-accordion" data-tabs-container>
<ul class="tabs-to-accordion__list" role="tablist" aria-label="">
<li class="tabs-to-accordion__list-item" data-tabs-button-wrap>
<button class="tabs-to-accordion__button" role="tab" aria-selected="false" data-tabs-button>
Test Heading
</button>
</li>
<li class="tabs-to-accordion__list-item" data-tabs-button-wrap>
<button class="tabs-to-accordion__button" role="tab" aria-selected="false" data-tabs-button>
Test Heading II
</button>
</li>
<li class="tabs-to-accordion__list-item" data-tabs-button-wrap>
<button class="tabs-to-accordion__button" role="tab" aria-selected="false" data-tabs-button>
Test Heading III
</button>
</li>
</ul>
<div class="tabs-to-accordion__window" data-tabs-panel-window>
<div class="tabs-to-accordion__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-to-accordion__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-to-accordion__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-to-accordion" data-tabs-container>
<ul class="tabs-to-accordion__list" role="tablist" aria-label="{{ heading }}">
{% for tab in tabs %}
<li class="tabs-to-accordion__list-item" data-tabs-button-wrap>
<button class="tabs-to-accordion__button" role="tab" aria-selected="false" data-tabs-button>
{{ tab.heading }}
</button>
</li>
{% endfor %}
</ul>
<div class="tabs-to-accordion__window" data-tabs-panel-window>
{% for tab in tabs %}
<div class="tabs-to-accordion__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>"
}
]
}
.tabs-to-accordion {
&__list {
margin: 0;
padding: 0;
font-size: 0;
}
&__list-item {
display: block;
margin: 0;
list-style: none;
font-size: 0;
@media (min-width: $breakpoint-sm) {
display: inline-block;
}
}
&__list-item + &__list-item {
@media (min-width: $breakpoint-sm) {
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;
}
}
}
&__button[aria-selected='true'] + &__panel {
display: block;
}
&__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;
}
(function() {
// Tabs Functionality
const components = document.querySelectorAll('[data-tabs-container]');
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];
let tabSiblings = getSiblings(tabWrap);
let panelSiblings = getSiblings(panel);
tabs[i].setAttribute('aria-selected', 'true');
tabs[i].setAttribute('tabindex', '0');
panels[i].setAttribute('data-tabs-panel-active', 'true');
tabSiblings.forEach((sibling) => {
sibling.querySelector('[data-tabs-button]').setAttribute('aria-selected', 'false');
sibling.querySelector('[data-tabs-button]').setAttribute('tabindex', '-1');
});
// Need to adjust this declaration to be specfic bbl
if (components[0].offsetWidth <= 385) {
panels.forEach((panel) => {
panel.setAttribute('data-tabs-panel-active', 'false');
})
}
});
// 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;
};
function mobileCheck() {
for (let i = 0; i < components.length; i++) {
const panelsWindow = components[i].querySelectorAll('[data-tabs-panel-window]')[0];
if (components[i].offsetWidth <= 385) {
for (let i = 0; i < tabs.length; i++) {
const associatedPanel = panels[i];
tabs[i].after(associatedPanel);
panelsWindow.innerHTML = '';
}
} else {
if (!panelsWindow.hasChildNodes()) {
panels.forEach((panel) => {
panelsWindow.prepend(panel);
});
}
}
}
}
if (tabs.length > 0) {
tabs[0].click();
}
window.addEventListener('resize', () => {
mobileCheck();
});
})()
This component displays content in a tabbed format on desktop, and transitions to display the content in an accordion on 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"
.Space
or Enter
= Activate tabs and accordion panels←
→
= Cycles tab and accordion panel focusControl + Option
←
→
= Navigate through tab and accordion content with screen reader