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?

Well the answer is yes, it is possible. With the use of some ARIA attributes and some simple JavaScript it is possible to make an accessible accordion pattern that can be understood and used by all site visitors.

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.

Accordions used on a dental implants website
Accordions used on a dental implants website.

By the way, if you were thinking you could use HTML5’s <details> and <summary> native elements, so you don’t have to code the accordion yourself, check out part 2 of this blog to see why those elements are unfortunately currently not as accessible as my suggested pattern below.

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:

  1. Can be operated by a mouse click.
  2. Can be operated by touch – on smartphones, tablets or desktops/laptops with touch screens.
  3. Can be operated by keyboard – important for sighted keyboard-only users, and desktop/laptop screen reader users.
  4. Can be operated easily by speech recognition software, like Dragon NaturallySpeaking.
  5. Can be operated easily by users with screen magnification tools.
  6. Can be easily understood by screen readers – are they aware the component is interactive, and whether the content is hidden or not.
  7. It must be possible to close all the accordion panels if required.
  8. It is not reliant on JavaScript to reveal content
  9. The accordion should look good, and signal functionality to sighted users.

Existing patterns

Sadly, many accordion components supplied by JavaScript libraries don’t satisfy all these requirements, and so will possibly prevent some users from accessing all the content. There are also some accordions which rely on CSS stylesheet properties to make them function. While these can be a good way of learning about the power of CSS, the stylesheet properties alone cannot give the full functionality that we require.

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="tab", 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.

Progressive enhancement

Before I go into code, it’s important to clarify requirement 8 from my list above. If you look in detail at the browsing stats for your website (or any website), you will likely see that a small percentage of site visitors have JavaScript inactive for some reason. That might be because they are in an office environment that blocks JavaScript in desktop machines, because of perceived security issues, or simply because the JavaScript files for your site did not load.

For this reason, I want my accordion component to still deliver useful content to site visitors even if JavaScript is not running for some reason. In the example of a frequently asked questions page above, if JavaScript is not running I want all the answers to be visible after the corresponding questions, and in a format that makes sense to screen reader users.

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.

After the initial setup – the underlying HTML structure as delivered into the browser – JavaScript will be used to add in the other elements. I’m not going to detail the actual JavaScript commands to use here as they would depend on the JavaScript library or libraries that are available to you, or run in your site.

Initial setup

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.

As we may have other content on the page as well as the FAQs, we’ll need to put the entire accordion into a container that we can refer to to limit the scope of our JavaScript. Without this, the rest of the content on the page might effectively become the answer to the last question.

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

The first step for our JavaScript setup is to add an interactive element for each question – a <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.

So our JavaScript should place the button element inside the heading, with the original heading text now becoming the button text.

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

Some more JavaScript heavy lifting is required here to wrap the answer to each question into a container that can be hidden and shown as required. In our example, our answer is just one paragraph, but in real life the answer could be many paragraphs, or other elements.

So we need to get our JavaScript to look for the next heading (of the correct level) within the accordion container, and enclose it all.

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

Accordions used in Wikipedia, when viewed on smartphone.
Accordions used in Wikipedia, when viewed on smartphone.

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.

Focus control

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:

  1. Tweak the class or remove the hidden attribute to reveal the panel.
  2. Change the value of the aria-expanded attribute to ‘true’.
  3. 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:

  1. Tweak classes or add the hidden attribute to hide the panel.
  2. Change the value of the aria-expanded attribute to ‘false’.
  3. Keep focus on the button.

Screen readers will typically voice “collapsed” when this has happened.

Other enhancements

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.

Styling notes

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 aria-expanded attribute.

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:

Working accessible accordion example.

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.

Want more?

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.