My favourite accessible accordion pattern
Accordions are useful components in web pages for hiding some content from users who may not need to, or wish to read it just now. Typical uses include ‘Frequently Asked Questions’ (FAQ) pages, or hiding sections of a web page when shown on smaller devices.
So is it possible to make an accessible accordion component?
This post looks at the requirements for an accessible accordion component, and shows you how to build one. I’ll also talk about why I favour the pattern presented here over others that are available.
Requirements for an accessible accordion
Before we start to consider the various options for accordion patterns, it’s useful to think about the key requirements that would make up an accessible accordion.
So here’s my list of must-haves – an accordion:
- Can be operated by a mouse click.
- Can be operated by touch – on smartphones, tablets or desktops/laptops with touch screens.
- Can be operated by keyboard – important for sighted keyboard-only users, and desktop/laptop screen reader users.
- Can be operated easily by speech recognition software, like Dragon NaturallySpeaking.
- Can be operated easily by users with screen magnification tools.
- Can be easily understood by screen readers – are they aware the component is interactive, and whether the content is hidden or not.
- It must be possible to close all the accordion panels if required.
- The accordion should look good, and signal functionality to sighted users.
However, like many custom controls, it is possible to find accessible examples of working accordions on the web. Amongst others, there are examples from W3C (Worldwide Web Consortium), Sitepoint, Scott O’Hara and Heydon Pickering. These are all good examples, but for one reason or another, they aren’t quite what I’m looking for.
There are examples out there (including Vanilla Accessibility) that use the
role="tabpanel" method of markup for the accordions. This method shares ARIA roles with tab panels, and its use with accordions has been deprecated now. So I wouldn’t recommend following that model.
Here I’ll present my ideal solution, showing my thinking, so you can compare methods to see which one works for you.
Does it make sense semantically?
Using our FAQ example, I want the questions to be easily found by everyone, so it would be sensible to make them properly marked up headings. Headings are useful for sighted users, and are easily found by screen reader users too.
As well as being easily found, we also want the question to be an interactive element too, so that people can activate it to hide and show the answer to the question. So the best element to use is a button.
Let’s build an accessible accordion
I’m going to continue using the FAQ ‘question and answer’ accordion as my example here, and will to try to explain in each step what I’m doing and why it’s important for accessibility.
Our initial setup will consist of a series of headings (all at the same level) that will form the ‘questions’, and then a paragraph or two or some other content after each heading, which will provide the answer.
So here is our initial code:
<div class="accordion-container"> <h2>How do I sign up for newsletters?</h2> <p>Simply fill out our membership form and you'll start getting the newsletters straight away.</p> <h2>Can I control the frequency?</h2> <p>When you sign up you can select the frequency of the newsletters.</p> --- other questions and answers --- </div>
Add interactive elements
<button> element. Since we’re going to need to refer directly to it later we’ll also generate a unique id for it.
However we don’t want to replace the heading with a
button, or use
role="button" to imply it is a button – doing that would mean that the heading would no longer show up in a screen reader’s list of headings on the page, which is useful functionality used by many.
Our first question should now look like this:
<h2><button id="accordion-button-01">How do I sign up for newsletters?</button></h2>
Create and hide the answer panels
Once again, other elements will need to refer to this panel directly so let’s give it an appropriate id value. We also want it hidden by default so let’s give it an appropriate class name that our CSS will recognise. The panel should be hidden using display:none to ensure that it’s hidden from all users and assistive technologies. Alternatively we could use the
hidden attribute if we wanted – since browser/screen reader support for that attribute is pretty good now.
It should now look like this:
<div id="accordion-panel-01" class="accordion-panel hidden"> <p>Simply fill out our membership form and you'll start getting the newsletters straight away.</p> </div>
Add in ARIA properties
ARIA properties can help screen readers understand the relationship between elements in a page.
Here we’ve got an interactive element that controls another element, so we could use the
aria-controls attribute on the <button> to define that relationship. The JAWS screen reader implements a special keystroke which allows users to jump to the controlled content. Other screen readers don’t have this currently, but may implement it in the future – so I think it’s worth putting it in. Note that
aria-controls is expecting its value to be the id of another element.
We can also use the <button> text to label the panel that gets opened – this helps screen reader users understand the structure of the page when they are moving around. This labelling is best done by using the
aria-labelledby attribute on the panel. Note that
aria-labelledby is expecting its value to be the id of the labelling element.
Putting it all together:
<h2><button id="accordion-button-01" aria-controls="accordion-panel-01"> How do I sign up for newsletters?</button></h2> <div id="accordion-panel-01" aria-labelledby="accordion-button-01" class="accordion-panel hidden"> <p>Simply fill out our membership form and you'll start getting the newsletters straight away.</p> </div>
Add in ARIA states
The last bit of ARIA we need to add in for screen reader users is the ‘state’ of the panel – ie. whether it’s open or not. For this purpose we should use the
aria-expanded attribute. But rather than putting it onto the panel itself, we place this attribute onto the button, as that’s the element that the screen reader user is interacting with.
aria-expanded is a boolean attribute, which means that its possible values are ‘true’ or ‘false’. Since the panels are hidden at the start, we should initially set the value of the attribute to ‘false’ – which will typically cause screen readers to announce “collapsed” or something similar.
So our example will now look like this:
<h2><button id="accordion-button-01" aria-controls="accordion-panel-01" aria-expanded="false"> How do I sign up for newsletters?</button></h2> <div id="accordion-panel-01" aria-labelledby="accordion-button-01" class="accordion-panel hidden"> <p>Simply fill out our membership form and you'll start getting the newsletters straight away.</p> </div>
When the panel is opened, the value of the
aria-expanded attribute should be changed to ‘true’. Screen readers will now typically voice this as “expanded”. Toggling the value of the
aria-expanded attribute in this way takes away the need for updating the button text for screen reader users.
We’ve now set up all the various ARIA attributes, but there’s one other piece of the equation that will make life easier for keyboard and screen reader users…
When the panel is opened I think it’s best to put focus onto the panel itself. My thinking here is that the user has specifically asked to view the answer to a question, why not help them find it?
To make sure the panel can get focus successfully in all browsers we need to add the special case
tabindex="-1" attribute to the panel itself. This attribute value prevents the panel from being part of the tab order of the page, but allows focus to be placed on it by scripting.
So after that’s added we now have:
<h2><button id="accordion-button-01" aria-controls="accordion-panel-01" aria-expanded="false"> How do I sign up for newsletters?</button></h2> <div id="accordion-panel-01" aria-labelledby="accordion-button-01" tabindex="-1" class="accordion-panel hidden"> <p>Simply fill out our membership form and you'll start getting the newsletters straight away.</p> </div>
Making it work
When the user activates the button (with the Enter key or Space bar) when the panel is hidden, the following things should happen – in this order:
- Tweak the class or remove the hidden attribute to reveal the panel.
- Change the value of the aria-expanded attribute to ‘true’.
- Move the focus onto the opened panel.
Now our example should look like this:
<h2><button id="accordion-button-01" aria-controls="accordion-panel-01" aria-expanded="true"> How do I sign up for newsletters?</button></h2> <div id="accordion-panel-01" aria-labelledby="accordion-button-01" tabindex="-1" class="accordion-panel"> <p>Simply fill out our membership form and you'll start getting the newsletters straight away.</p> </div>
To close the panel again, the user will once again activate the button. When that is done, the following things should happen – in this order:
- Tweak classes or add the hidden attribute to hide the panel.
- Change the value of the aria-expanded attribute to ‘false’.
- Keep focus on the button.
Screen readers will typically voice “collapsed” when this has happened.
Some accessible accordion patterns give the accordion panels a
role="region" attribute. Along with the use of the
aria-labelledby attribute this creates a landmark role on the page – so makes it more easily findable by screen reader users.
Without styling, the button in the heading will show in the browser with a default button style which may not be what is required. It’s possible to style the buttons in another way but for sighted users it’s best to provide some kind of indication that activating the button does something, or the functionality may be missed.
Examples of indications could include downward pointing arrows, or the plus symbol (+). When the panel is open, the corresponding styles could be attached to the button – eg upward pointing arrow or minus symbol (-).
These styles can be attached to the button using CSS attribute selectors, based on the value of the
A working example of an accessible accordion
So you can see this in action, I’ve created a page that shows a working example of the accessible accordion pattern that I favour. It’s based on the techniques covered above:
What do you think?
We hope these insights are useful to you, and would love to hear your experiences around implementing accessible accordions. Please share your comments below.
If this blog has been useful, you might like to sign-up for the Hassell Inclusion newsletter to get more insights like this in your email every month.