wai-tabs
v0.0.4
Published
A simple Tabs widget skeleton, built with accessibility in mind - on top of `tabindex`, `aria-*` and `role` attributes.
Downloads
7
Maintainers
Readme
WAI Tabs ⛳
A simple Tabs widget skeleton, built with accessibility in mind - on top of tabindex
, aria-*
and role
attributes. It's a bare minimum, but lets you construct a fully functional module in minutes with little to no effort at the same time, if the correct CSS styles were provided.
Why
- There are tons of similar plugins available, but usually we don't need an extra functionality or dive into a setup complexity, these offer.
- This plugin lets you ensure that you didn't forget to add all required attributes to match WAI-ARIA Tabs Pattern.
- Ability to use any HTML tags as controls besides
<button>
. - You can use as many wrappers as you need for the navigation items or panels - this will not affect the behavior.
- Tab panels can be added in a different order than tabs - it doesn't matter since the script queries the panels by the
id
attribute.
How it works
Basically, it changes certain attributes, but also provides a simple API for extending: it doesn't add any classes, modify styles or change other attributes except tabindex
, aria-selected
and aria-hidden
, so you need to pass a custom function/hook to the mount
method if you want to change the HTML DOM dynamically - please see Configuration. You may ask: "Why the script doesn't add hidden
attribute to panels?" Well, because sometimes we don't need to entirely hide the panels, but toggle the CSS visibility property instead to keep the widget height constant, relying on the tallest pane, and using CSS transitions.
It utilizes the following HTML Attributes:
role="tablist"
is used as an entry selector. This can be changed - please see Configuration, but still, this attribute is considered required https://w3c.github.io/aria/#tablistrole="tab"
is used as a selector to query all navigation items.aria-orientation
("horizontal"
/"vertical"
) attribute is for internal handling the key codes correctly while using keyboard. It's optional, if your tabs list is horizontal.tabindex
("0"
/"-1"
) to make elements focusable or restrict focus instead.aria-selected
("true"
/"false"
) see https://w3c.github.io/aria/#aria-selected Additionally,aria-selected="true"
determines an initial index that's utilized internally while using arrow keys to focus on a tab.aria-controls
tab attribute along with the tab panelid
to query all associated pane elements.aria-hidden
is used to mark the pane as hidden to screen readers.
So, you should add attributes stated above to let the widget work. The script itself doesn't assert custom error messages to keep it lightweight.
Installation
npm i wai-tabs
then
import WAITabs from "wai-tabs"
Configuration
The very basic usage
new WAITabs().mount()
The script takes care about all instances on the page, querying elements by '[role="tablist"]'
selector by default.
A minimal CSS might look like this:
[role="tab"][aria-selected="true"] {
background-color: blue;
color: white;
}
[role="tabpanel"][aria-hidden="true"] {
display: none;
}
And Tabs HTML:
<div role="tablist" aria-orientation="horizontal">
<button id="tab-1" role="tab" aria-selected="true" tabindex="0" aria-controls="panel-1">Tab 1</button>
<button id="tab-2" role="tab" aria-selected="false" tabindex="-1" aria-controls="panel-2">Tab 2</button>
<button id="tab-3" role="tab" aria-selected="false" tabindex="-1" aria-controls="panel-3">Tab 3</button>
</div>
<div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1" aria-hidden="false">
Panel #1
</div>
<div id="panel-2" role="tabpanel" tabindex="-1" aria-labelledby="tab-2" aria-hidden="true">
Panel #2
</div>
<div id="panel-3" role="tabpanel" tabindex="-1" aria-labelledby="tab-3" aria-hidden="true">
Panel #3
</div>
Advanced usage
Since we might need to have more control over the look and behavior by using custom classes or change the HTML DOM dynamically, the code might look like this:
/**
* This function invokes per a separate instance initialization and
* may optionally return an object with two optional properties
* - callbacks "onSelect" and "onTabKeyFocus"
*/
const useInstance = (singleInstance) => {
const {
wrapper, // [role="tablist"] element by default or <div class="my-tabs">...</div> in our case
navItems, // an array of [role="tab"] elements
panes, // an array of associated [role="tabpanel"] elements
getIndex, // a function/method - get an internal active tab's index
selectTab // a function/method - programmatically select a desired tab, passing a certain index as an argument
} = singleInstance;
// Adding custom data attribute just as an example
const initialIndex = getIndex();
panes[initialIndex].dataset.currentPane = "true";
return {
/**
* Fires when a tab is selected on the "click" or "keydown"
* event or after invoking the "selectTab" method (event as
* a second argument will be undefined).
*/
onSelect: (index, event) => {
console.log(event.type === "click" ? `Clicked the tab #${index + 1}` : `Pressed the tab #${index + 1}`);
navItems.forEach((navItem, i) => {
// I personally use Tailwind, so I need to add/remove a certain class
navItem.classList[i === index ? "add" : "remove"]("text-blue-400", "border-b-blue-400");
// Change pane's "data-current-pane" attribute
panes[i].dataset.currentPane = (i === index).toString();
})
},
/**
* Fires when focused on a tab, but only if keyboard
* keys such as arrows, Home, End were used
*/
onTabKeyFocus(index, event) {
console.log(`The tab #${index + 1} is in focus!`, event.type);
}
};
};
new WAITabs(".my-tabs").mount(useInstance);
Run a local dev server
npm i && npm start