A11y Toggle
Get the code on GitHub or skip to a specific documentation section right away:
- Getting started
- Basic example
- Expanded by default
- Non-button toggles
- Multi toggles
- Animations
- Connected toggles
- Dynamically injected toggles
Pure CSS toggles using the checkbox hack introduce some usability and accessibility problems.
For starters, the checkbox hack relies on the :checked
pseudo-class, which is not supported everywhere. For instance, BlackBerry browser, Opera Mini, Android Stock Browser and Firefox Android (amongst others) do not support this selector, making the whole thing broken.
On top of that, in order to be fully accessible a content toggle needs some extra attributes that cannot be toggled without JavaScript. The toggle itself needs a aria-controls
attribute linking to the expandable element, and a aria-expanded
attribute describing its state (expanded or collapsed). The collapsed element itself needs the aria-hidden
attribute when invisible.
a11y-toggle takes care of all these considerations for you. Initial necessary attributes are being added by the script so you don’t even have to care about this.
No more excuse now. Make your toggles accessible.
Getting started
To get started, simply include a11y-toggle.js (ideally the minified version, of course) in your document. It can be either in the <head>
or at the bottom of the <body>
although the latter the better, for performance reasons.
<script src="a11y-toggle.min.js" async></script>
That’s it! There is no need to initialize anything as everything else is being handled by the script itself. Simply check the next section to see how to declare your toggles.
Basic example
The bare minimum is a button with the data-a11y-toggle
attribute mapped to an existing id
in the document. You can have a look at the code below.
<button type="button" data-a11y-toggle="target">
Toggle code »
</button>
<div id="target">
Some content…
</div>
Initial ARIA-specific attributes such as aria-expanded
, aria-hidden
and aria-labelledby
, as well as id
on the toggle element (if none already) are being added automatically.
On the CSS Side, only hiding aria-hidden
content is strictly necessary. It is also recommended to hide the button when JavaScript is disabled, by checking the presence of the aria-controls
attribute for instance.
[aria-hidden='true'],
[data-a11y-toggle]:not([aria-controls]) {
display: none;
}
Note: globally hiding elements with [aria-hidden="true"]
can have rendering issues with third party integrations. Only known to date is with Google Maps, in which it prevents the map tiles to render. Therefore it needs to be resetted inside a Google Maps container.
See the Pen a11y-toggle — Basic example by Kitty Giraudel (@KittyGiraudel) on CodePen.
Expanded by default
If you want the content to be expanded by default, you can add a data-a11y-toggle-open
attribute to the target element (not the toggle itself).
Note that this is the default behaviour when JavaScript is disabled, so that the content remains accessible even without JavaScript.
<button type="button" data-a11y-toggle="target">
Toggle code »
</button>
<div id="target" data-a11y-toggle-open>
Some content that is expanded by default…
</div>
See the Pen a11y-toggle — Expanded by default by Kitty Giraudel (@KittyGiraudel) on CodePen.
Multi toggles
A collapsible container can have several toggles able to control its visibility. There is no difference in markup regarding multi toggles.
<button type="button" data-a11y-toggle="target">
Toggle code »
</button>
<button type="button" data-a11y-toggle="target">
Toggle code »
</button>
<div id="target">
Some content…
</div>
See the Pen a11y-toggle — Multi toggles by Kitty Giraudel (@KittyGiraudel) on CodePen.
Animations
Given that a11y-toggle is completely unopinionated regarding the styling layer, it is really up to the developer to implete the sliding and/or fading animation. Here is an example below. Hat tip to Nicolas Hoffman.
The markup does not change compared to the original version except that we add a class to the collapsible sections to be able to target them in CSS.
<button type="button" data-a11y-toggle="target">
Toggle code »
</button>
<div class="collapsible-box" id="target">
Some content…
</div>
The styles do not rely on display: none
anymore but a tricky / hacky combination of declarations to make the magic happen.
.collapsible-box {
overflow: hidden;
opacity: 1;
max-height: 80em;
visibility: visible;
transition:
visibility 0s ease,
max-height 2s ease,
opacity 2s ease;
transition-delay: 0s;
}
.collapsible-box[aria-hidden='true'] {
max-height: 0;
opacity: 0;
visibility: hidden;
transition-delay: 2s, 0s, 0s;
}
See the Pen a11y-toggle — Animations by Kitty Giraudel (@KittyGiraudel) on CodePen.
Connected toggles
The library does not provide out-of-the-box support for connected toggles, which are a set of toggles with only one expanded at any time.
However with a tiny bit of JavaScript, it is possible to leverage the capabilities of the library to add this feature.
<div class="connected-toggles">
<button data-a11y-toggle="target-1" type="button">
Toggle content 1 »
</button>
<button data-a11y-toggle="target-2" type="button">
Toggle content 2 »
</button>
<div id="target-1">
Some content (1)…
</div>
<div id="target-2">
Some content (2)…
</div>
</div>
The trick is to collapse all targets in the set when one is being activated, except this one. The JavaScript snippet to achieve that should be pretty straight-forward.
function collapse (toggle) {
var id = toggle.getAttribute('data-a11y-toggle');
var collapsibleBox = document.getElementById(id);
collapsibleBox.setAttribute('aria-hidden', true);
toggle.setAttribute('aria-expanded', false);
}
function collapseAll (event) {
toggles
.filter(function (toggle) {
return toggle !== event.target;
})
.forEach(collapse);
}
var toggles = Array.prototype.slice.call(
document.querySelectorAll('.connected-toggles [data-a11y-toggle]')
);
toggles.forEach(function (toggle) {
toggle.addEventListener('click', collapseAll);
});
Note that if you have several instances of connected toggles on your page, you might want to read this issue, as the current code is too simple to cover this edge case.
Beware! a11y-toggle is not a library to build tabs. While it certainly is possible to do so, remember that tabs imply other accessibility concerns which should be taken care of.
See the Pen a11y-toggle — Connected toggles by Kitty Giraudel (@KittyGiraudel) on CodePen.
Dynamically injected toggles
a11y-toggle fires once when the DOM is fully loaded (DOMContentLoaded
). You will have to relaunch it if you dynamically add new toggles with JavaScript.
Thankfully, a11y-toggle exposes an a11yToggle
function on the global object.
window.a11yToggle();
If you do not want to reinitialise everything, you can pass a context to the a11yToggle
function which will be used as a root for .querySelectorAll
.
var newContainer = document.getElementById('new-container');
window.a11yToggle(newContainer);