Accessible tabbed content (vanilla JS)
Adds tab content behaviour with associated ARIA roles and hints.
by shakibdshy
HTML
<ul class="tabs" data-directive="tabs">
<li><a href="#panel-01" data-behaviour="tab">Tab 1</a></li>
<li><a href="#panel-02" data-behaviour="tab">Tab 2</a></li>
<li><a href="#panel-03" data-behaviour="tab">Tab 3</a></li>
</ul>
<div id="panel-01">
Tab content 1
</div>
<div id="panel-02">
Tab content 2
</div>
<div id="panel-03">
Tab content 3
</div>
<ul class="tabs" data-directive="tabs">
<li><a href="#panel-04" data-behaviour="tabs">Tab 1</a></li>
<li><a href="#panel-05" data-behaviour="tabs">Tab 2</a></li>
<li><a href="#panel-06" data-behaviour="tabs">Tab 3</a></li>
</ul>
<div id="panel-04">
Tab content 1
</div>
<div id="panel-05">
Tab content 2
</div>
<div id="panel-06">
Tab content 3
</div>
CSS
body {
font:normal 1.25em/1.5 Arial, sans-serif;
}
:focus {
outline:1px dotted #EF9600;
outline-offset:1px;
}
.tabs {
margin: 0;
padding: 0;
border-bottom: 1px solid #EEE;
}
.tabs > li {
display: inline;
}
.tabs > li > a {
display: inline-block;
padding: .25em .5em;
border: 1px solid #EEE;
border-bottom: 0;
text-decoration: none;
background: #EEE;
}
.tabs > li > a.is-selected {
margin-bottom: -1px;
border-bottom: 1px solid #FFF;
background: #FFF;
}
JavaScript
(function (name, context, definition) {
if (typeof define === 'function' && define.amd) {
define(definition);
}
else if (typeof module !== 'undefined' && module.exports) {
module.exports = definition();
}
else {
context[name] = definition();
}
})('Tabs', this, function() {
'use strict';
/**
* Tabs
* @constructor
* @param {HTMLElement} element
*/
function Tabs(element) {
var i, len;
this.target = element;
this.tabs = element.querySelectorAll('[data-behaviour=tab]');
this.panels = [];
for (i = 0, len = this.tabs.length; i < len; i++) {
this.panels.push( document.getElementById(this.tabs[i].hash.replace('#', '')) );
}
if (this.selectedIndex === undefined) {
this._init();
}
}
/**
* Init
* @private
*/
Tabs.prototype._init = function() {
var i;
var self = this;
this.target.setAttribute('role', 'tablist');
for (i = this.tabs.length - 1; i >= 0; i--) {
var tab = this.tabs[i];
var panel = this.panels[i];
var preSelected = tab.className.match(/\bis-selected\b/);
var selected = i === 0 || preSelected || window.location.hash.replace('#', '') == panel.id;
tab.setAttribute('role', 'tab');
tab.setAttribute('aria-selected', selected);
tab.setAttribute('aria-controls', tab.hash.replace('#', ''));
panel.setAttribute('role', 'tabpanel');
if (selected) {
this.selectedIndex = i;
if (!preSelected) {
tab.className+= ' is-selected ';
}
}
else {
panel.style.display = 'none';
}
}
this.clickHandler = function(e) {
var target = e.srcElement || e.target;
if (target.getAttribute('role') ==...