Accessibility by Default: Tabs, Accordions and Disclosures
Hi and welcome back to Accessibility by Default. This week we’re looking at three components that are usually grouped together because they all show and hide content: tabs, accordions and disclosures.
Although they all reveal and hide content, these components aren’t interchangeable. Each has a specific purpose, and choosing the right one changes how users navigate your content and what they expect when interacting with it.
What are tabs, accordions and disclosures?
Tabs
Tabs are used to switch between related sections of content so only one panel is visible at a time. Usually you’ll have a navigation panel at the top of the section and depending on which you choose will show the relevant information. These could be used if you visit an online store you may have a tab for description, a tab for reviews and a tab for related products.
Accordion
Accordions are comprised of a vertically stacked list of sections that can be expanded and collapsed. You have a header section which is a button to trigger the expand or collapse of the hidden information. Depending on the implementation it could be configured so that when one section expands it’ll collapse all of the others or it could be configured so that all sections can be open at the same time. These are great if you have a long list of FAQs
Disclosures
In my opinion these are the simplest of the three. You have one control, one section of hidden content, and you toggle it open or closed. This to me is like having a single section of an accordion as opposed to multiple sections. These can be used if you only have one piece of hidden information such as “Show advanced options”
When should you use each?
So now that we know what each component is let’s look at the different use cases for them because as I said before these aren’t interchangeable.
Tabs
- best for small number of equal sections
- avoid when there are large amounts of content
Accordions
- best for independent expandable sections
- avoid when users want to compare information side by side
Disclosure
- best for optional supporting information
- avoid using this for primary page content
Tabs work well when users are switching between related information or want to compare the different information. Accordions should be used when users only care about one section that is independent from the rest (but the information can still be, somewhat, related). Disclosure is ideal when its not information that users need to see all of the time.
One of the biggest accessibility mistakes isn’t forgetting an aria attribute (but please don’t do that) it’s choosing the wrong component.
A good rule of thumb is to ask yourself a simple question:
Get Josh Allan’s stories in your inbox
Join Medium for free to get updates from this writer.
Can users benefit from seeing more than one section at once?
- If yes, consider an accordion.
- If no, tabs may be the better choice.
- If it’s only a single optional piece of information, use disclosure.
Accessibility considerations
Tabs
So when we’re looking at the accessibility side of tabs we need look at the roles of each different part. The element that is the tab component container should have role=”tablist”, each element that is a tab should have role=”tab” and the section that contains the hidden content should have role=”tabpanel”. Now that everything has a role let’s look at the aria side. Each element that has a role=”tab” should also have an aria-controls which will refer to the tabpanel component, the active tab should have aria-selected=”true” and the non-selected should aria-selected=”false”. Each element with role=”tabpanel” should have aria-labelledby which refers to the associated tab. And finally, depending on the orientation of the tablist, if it is vertical it should have aria-orientation=”vertical” (the default is horizontal so no need if that is your orientation).
Now, those are just the roles and arias needed but there are other accessibility considerations such as the left and right arrows should move to either the previous tab or the next tab. Space or enter should activate the tab that you’re currently on. Those are the mandatory interactions but there are also some optional ones; home and end should move to either the first or last tab, delete should close the current tab and set focus to the following tab
<div role="tablist" aria-label="Product information">
<button
id="tab-description"
role="tab"
aria-selected="true"
aria-controls="panel-description">
Description
</button>
<button
id="tab-reviews"
role="tab"
aria-selected="false"
aria-controls="panel-reviews">
Reviews
</button>
<button
id="tab-related"
role="tab"
aria-selected="false"
aria-controls="panel-related">
Related Products
</button>
</div>
<div
id="panel-description"
role="tabpanel"
aria-labelledby="tab-description">
<p>This is the description panel.</p>
</div>
<div
id="panel-reviews"
role="tabpanel"
aria-labelledby="tab-reviews"
hidden>
<p>This is the reviews panel.</p>
</div>
<div
id="panel-related"
role="tabpanel"
aria-labelledby="tab-related"
hidden>
<p>This is the related products panel.</p>
</div>Accordion
After that exhaustive list on tabs, thankfully the accordion pattern isn’t as big. For roles and arias you need the title of each header should be contained in an element with role=”button”, if the associated panel is visible the header button should have aria-expanded=”true” otherwise it should be set to “false”, the button element should have aria-controls set to the ID of the element containing the accordion panel content.
And in terms of keyboard interaction, space / enter should active the accordion section to either show or hide the associated content, tab should move to the next focusable element and shift + tab should take you to the previous focusable element.
<h2>
<button
aria-expanded="false"
aria-controls="faq-1"
id="faq-button-1">
What is your returns policy?
</button>
</h2>
<div
id="faq-1"
role="region"
aria-labelledby="faq-button-1"
hidden>
<p>
You can return any item within 30 days of purchase.
</p>
</div>Disclosure
Unlike tabs, disclosure already has a native HTML implementation using <details> and <summary>. This gives us much of the accessibility behaviour for free, making it my preferred approach whenever it meets the design requirements. For roles and states, the element that shows and hides should have a role=”button” and when the content is visible it should have aria-expanded=”true” otherwise it should be aria-expanded=”false” and optionally the element with role button has a value specified for aria-controls that refers to the element that contains all the content that is shown or hidden. For keyboard interaction it should expand / collapse using either space or enter.
If you are using native <details> and <summary>, you do not need to manually add role="button" or aria-expanded. The browser's user agent stylesheet and accessibility tree handle the toggle semantics and expansion state natively out of the box.
If you’re building a custom disclosure (e.g., using a <div> or a <button> controlling an external panel), then you need aria-expanded.
<details>
<summary>Show advanced options</summary>
<p>
These settings are intended for advanced users and should only
be changed if you understand their impact.
</p>
</details>The great thing about using <details> and <summary> is the built-in accessibility that comes with it, because its using the native HTML.
Common mistakes
These are the most common mistakes that i see people make when implementing these components
- Using tabs for unrelated content
- Making the accordion headers not focusable (remember
role=”button”) - Hiding the content visually but keyboard users are still able to access it
- Using accordions for everything
Key takeaways
These are my key takeaways for these components. Remember to use the component based on your need not just for styling options. Use native HTML where possible as you get the accessibility for free. Don’t forget about keyboard support and interaction and communicate aria when required
Accessibility by Default is a series where I explore practical accessibility techniques from the perspective of a frontend engineer. Rather than repeating specifications, the goal is to explain the engineering decisions and patterns that make accessible interfaces easier to build from the start.
You can find the full series on my profile









