a11y_tab_widgets
v2.0.1
Published
ES5 script to create accessible tab widget components
Maintainers
Readme
Accessible Tabbed Interfaces
A script to progressively enhance sectioned content into an accessible tabbed interface.
How to use
To help facilitate the simplest integration with your code base, the required markup is as lean as possible.
Minimum Setup
<div data-atabs>
<div data-atabs-panel
data-atabs-tab-label="Tab label goes here">
<!-- all panel content goes here -->
</div>
<section data-atabs-panel>
<h# data-atabs-label>
<!--
The text/markup injected the panel's
associated role="tab" element.
-->
</h#>
</section>
<!-- repeat as necessary -->
</div>
<!-- ... -->
<script src="index.js"></script>
<script>
var widget = '[data-atabs]';
var els = document.querySelectorAll(widget);
// Generate all Tab Widget instances
for ( var i = 0; i < els.length; i++ ) {
var nTabs = new ARIAtabs( els[i] );
}
</script>data-atabs attributes & options
The script runs through the minimum markup looking for specific data-atabs-* to use as hooks to modify the original markup and generate the final Tab Widget.
data-atabs
The primary hook. This attribute is used to contain the final Tab Widget.data-atabs-toc
Without JavaScript, a table of contents (TOC) can provide easy access to different sections of a document that would have otherwise been part of the Tab Widget. With JavaScript available, the TOC isn't as necessary. Providing this attribute with theidof the TOC will remove the TOC from the DOM.data-atabs-automatic
If this attribute is set to thedata-atabswrapper of any Tab Widget in a document, it will make all Tab Widgets automatically reveal thetabpanelassociated with the currently focusedtabelement. The reason this globallly affects Tab Widgets is to reduce any possibility of an inconsistent user experience between different Tab Widgets.data-atabs-orientation
If this attribute is set to thedata-atabswrapper element, and it's value is set to "vertical", then it will addaria-orientation="vertical"to thetablistand modify the arrow keys from left and right to up and down to move focus through thetabs within thetablist.data-atabs-swap-orientation
TODO Use this attribute on thedata-atabswrapper and provide it a UNIT value to indicate when a Tab Widget'stablistshould change from "vertical" to "horizontal" orientation for responsive design interfaces.data-atabs-panel
Designates that an element should serve as atabpanel. If given the value of "default", the script will set thistabpaneland associatedtabto be active, instead of the firsttabandtabpanel. If multipledata-atabs-panelattributes have the value of "default", only the first one will be respected.data-atabs-tab-label
Also used on the element that will be atabpanel, this attribute indicates that the generatedtabshould use its value as thetab's label. The value ofdata-atabs-tab-labeltakes precedents over using the content of thetabpanel's heading when generating thetab.data-atabs-heading
Place this attribute on the element that serves as the heading within thetabpanel. Unless adata-atabs-tab-labelis used on thetabpanel, this heading will serve as the accessible name for the generatedtab. By default, elements with thedata-atabs-headingattribute will be removed after their content has been used for the generatedtab, unless the value of "keep" is set, e.g.data-atabs-heading="keep. Only the first instance of an element with this attribute will be recognized by the script.
Injecting tabpanels into the Tab Widget
Once a new instance of a Tab Widget has been created, the addTab function can be called from outside the script. Using this function, you can point to elements in the DOM that are not wrapped in the data-atabs element to inject them into the Tab Widget.
For instance:
<script src="index.js"></script>
<script>
var tabInstance = '[data-atabs]';
var els = document.querySelectorAll(tabInstance);
var injectContent = document.getElementById('inject-content');
var cloneContent = injectContent.cloneNode(true);
var allTabs = [];
// Generate all tab instances
for ( var i = 0; i < els.length; i++ ) {
var nTabs = new ARIAtabs( els[i] );
allTabs.push(nTabs);
}
// remove the original instance of the external content from the document.
injectContent.parentNode.removeChild(injectContent);
// Inject the external content into a particular
// tab, captured in the allTabs var.
allTabs[1].addTab(cloneContent, 'Tab label', 'add-a-class');
</script>User Experience
The manner in which you interact with a Tab Widget is dependent on your input device. Not all devices/assistive applications are listed here, but the following will give you a baseline of expectations if when testing this script, or comparing your own Tab Widget.
Mouse / Touch
Clicking or tapping a tab will set that tab to its selected state, and reveal its associated tabpanel, while deselecting and hiding the previously selected tab and its tabpanel.
Mouse / Touch + Screen Reader
If using a mouse while also using NVDA with the setting "Report role when mouse enters object", NVDA will announce "Tab. Accessible Name."
If using iOS with VoiceOver enabled, and exploring by touch, a tab should announce itself as "Accessible name. Tab. Number of Numbers". If the touched tab is currently active VoiceOver will announce "Selected" prior to the accessible name.
Keyboard
When interacting with a Tab Widget with a desktop or laptop keyboard, one can use the Tab key to navigate to the tablist. Keyboard focus will highlight the tab that is currently active.
If the tablist is horizontally orientated, using the Left and Right arrow keys to will navigate to the previous and next tabs in the tablist. Keyboard focus will loop from the last tab to the first, and vice versa. If the tablist is vertically oriented, Up and Down arrow keys will navigate the tabs. Note: vertically oriented tablists should have the attribute aria-orientation="vertical.
If a Tab Widget has a data-atabs-automatic set to it, then any Tab Widgets in the current document will automatically load the associated tabpanel of a tab when it receives focus via arrow keys.
Keyboard + Screen Readers
This section coming soon...
VoiceOver: MacOS
VoiceOver: iOS
Android Accessibility Suite (TalkBack)
JAWS
NVDA
Narrator
Dependencies
There are no major dependencies for this script. The WICG :focus-visible polyfill is used in the demonstration page, and has a class included in the CSS file. However, it is up to you if you would like to download the polyfill yourself.
Additional Reading
- ARIA Specification: Tab Role
- ARIA Specification: Tablist Role
- Aria Specification: Tabpanel Role
- WAI-ARIA Authoring Practices: Tab Widgets
License, Thank yous & Such
This script was written by Scott O'Hara: Website, Twitter.
It has an MIT license.
Special thanks to Josh Drumm for helping me with some JavaScript refactoring.
Use it, modify it, contribute to it to help make your project more accessible :)
