Toggling elements accessibly
Toggling is the act of showing or hiding an element. We usually do this via the user performing an action on a button
element.
It’s important to hide elements correctly to ensure screen-readers and input devices (i.e. a keyboard) cannot access them (unless you want them to) and also inform screen-readers that the button
they have in focus is a toggle button and what its current state is (i.e. is it 'expanded' or 'collapsed').
Basic example
This basic example has no animation so we can hide the element with the hidden
attribute, using code like this:
<button aria-controls="terms" aria-expanded="false" type="button">
Terms and conditions
</button>
<div id="terms" hidden>
<h2>Terms</h2>
<p>Terms and conditions will go here.</p>
<p><a href="terms.html">Further terms</a></p>
</div>
and then when expanded it looks like this…
<button aria-controls="terms" aria-expanded="true" type="button">
Terms and conditions
</button>
<div id="terms">
<h2>Terms</h2>
<p>Terms and conditions will go here.</p>
<p><a href="terms.html">Further terms</a></p>
</div>
Animated example
This example hides the '#terms' element in a different way because it needs to animate it. Remember hidden
acts like display: none
and you cannot animate/transition with CSS from display:none
. So we use the inert
attribute to hide the content from screen-readers and prevents its focusable child elements from coming into focus via a keyboard’s TAB key stroke.
<button aria-controls="terms" aria-expanded="false" type="button">
Terms and conditions
</button>
<div
id="terms"
inert
style="overflow: hidden; maxHeight: 0; transition: max-height 1s"
>
<h2>Terms</h2>
<p>Terms and conditions will go here.</p>
<p><a href="terms.html">Further terms</a></p>
</div>
then when expanded it looks like this…
<button aria-controls="terms" aria-expanded="true" type="button">
Terms and conditions
</button>
<div
id="terms"
inert
style="overflow: hidden; maxHeight: 500px; transition: max-height 1s"
>
<h2>Terms</h2>
<p>Terms and conditions will go here.</p>
<p><a href="terms.html">Further terms</a></p>
</div>
If we do not/cannot use inert
then we have to prevent the user from being able to TAB to any visually hidden child elements (like the a
element inside the #terms
div) and we do this by assigning interactive elements the attribute/value combo of tabindex="-1"
.
Like this:
<button aria-controls="terms" aria-expanded="false" type="button">
Terms and conditions
</button>
<div
aria-hidden="true"
id="terms"
inert
style="overflow: hidden; maxHeight: 0; transition: max-height 1s"
>
<h2>Terms</h2>
<p>Terms and conditions will go here.</p>
<p><a href="terms.html" tabindex="-1">Further terms</a></p>
</div>
then when expanded it looks like this…
<button aria-controls="terms" aria-expanded="true" type="button">
Terms and conditions
</button>
<div
id="terms"
style="overflow: hidden; maxHeight: 500px; transition: max-height 1s"
>
<h2>Terms</h2>
<p>Terms and conditions will go here.</p>
<p><a href="terms.html">Further terms</a></p>
</div>
Further reading