Accordion
UI pattern
An accordion lets users reveal only the sections they need, so a page with a lot of content takes less scrolling.
An accordion's sections can have headings or not, and it can let several sections be open at once or only one. This entry covers all of them. Headings put each section's title in the page's outline, so users can move between sections by heading.
Each section follows the disclosure pattern, and this entry carries the criteria a section's button and panel are tested on, along with what an accordion adds: the heading around each section's button and its level, the panel's role and name when its content earns a landmark, and the structure of the container. When the sections have headings, this entry recommends a heading element containing a button.
When to Use
Use an accordion when:
- The page has several related sections of content, with or without headings, and users benefit from showing only the ones they need rather than scrolling past all of them.
Give each section a heading, as this entry describes, when:
- Each section's title is meaningful as a heading and the sections belong in the page's outline, so users can reach them by moving from heading to heading.
A stack whose sections don't need headings is still an accordion, and this entry covers it too.
Do not use an accordion when:
- There's only one collapsible section. Use a disclosure.
- The sections are steps in a sequential process rather than independent topics. Use a pattern built for a sequence, such as a multi-step form.
- Hiding the content would bury information every user needs immediately, such as critical form fields or error messages. Show it.
Examples
Code in this section is meant to demonstrate techniques rather than requirements and lacks features needed in a production application.
The examples show techniques for an accordion whose sections have headings. For sections without headings, see the examples in Disclosure.
Accordion Using ARIA Disclosure With Buttons
The HTML presents the content in full, with each heading followed by a <section> that references its heading through aria-labelledby. That structure becomes an accordion as a progressive enhancement when JavaScript is enabled.
Using <section> elements with an aria-labelledby attribute gives each the region role and includes it in the list of page landmarks. The example shows the technique, but listing the sections among the page's landmarks is a design decision that short descriptions like these wouldn't normally warrant.
The example includes a technique to collapse other disclosures when one is expanded.
Techniques:
- The accordion builds on the ARIA Disclosure UI pattern, wrapping each controlling
<button>element with a section heading. - Using HTML
<button>elements as the disclosure controls makes them natively accessible, so they need onlyclickevent listeners. - The accordion headings are all at the same level, one level below the heading that introduces them.
- After the text of each button that JavaScript adds, CSS draws an icon indicating whether the section is open or closed. The icon follows the text so the heading text, which is rendered before the JavaScript runs, doesn't shift.
- To avoid layout shifts when JavaScript builds the accordion, CSS controls panel display depending on the availability of JavaScript, which is detected early. JavaScript then toggles the class
is-openon each panel.
ARIA accordion HTML
<h2>Popular Pets</h2>
<div class="accordion" data-accordion>
<h3
id="heading-1"
class="accordion-header"
>
Cats
</h3>
<section
class="accordion-panel"
aria-labelledby="heading-1"
>
A cat is a small, carnivorous mammal often adopted by a human family
because it is cute, does funny things, and purrs. They are often found
resting on laps, walking on laptops, or half-hiding around a corner
while peering at you.
</section>
<h3
id="heading-2"
class="accordion-header"
>
Dogs
</h3>
<section
class="accordion-panel"
aria-labelledby="heading-2"
>
A dog is a vociferous omnivore descended from wolves that long ago
domesticated humans after coaxing them to share food. The size and
shape of dogs vary widely but all enjoy playing with tennis balls.
</section>
</div>ARIA accordion CSS
The scripting media query matches in modern browsers with JavaScript enabled in user settings even when JavaScript is otherwise blocked. To cover that case, add a <script> element to the HTML <head> that sets a class on the document root, then scope the CSS with a selector.
<!-- Add to <head> to condition availability of JavaScript -->
<script>document.documentElement.classList.add('js')</script>@media (scripting: enabled) {
:root.js {
& .accordion-header > button {
width: 100%;
display: flex;
justify-content: space-between;
cursor: pointer;
}
& .accordion-header > button[aria-expanded]::after {
display: block;
content: "⌃" / "";
transform: rotate(0.5turn);
}
& .accordion-header > button[aria-expanded="true"]::after {
transform: rotate(0);
}
& .accordion-panel {
display: none
}
& .accordion-panel.is-open {
display: block
}
}
}ARIA accordion JavaScript
class Accordion {
#root
#buttons = []
constructor(root) {
this.#root = root
this.#root.querySelectorAll(':scope .accordion-header')
.forEach((header) => {
// create a button for each header
// setting type=button to prevent accidental form submission
const button = document.createElement('button')
button.type = 'button'
this.#buttons.push(button)
// wrap header contents
button.append(...header.childNodes)
header.append(button)
// derive panel id from header id
const panelId = header.id + '-panel'
header.nextElementSibling.id = panelId
// set aria attributes
button.setAttribute('aria-expanded', 'false')
button.setAttribute('aria-controls', panelId)
// add event listener to handle state changes
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') !== 'true'
button.setAttribute('aria-expanded', String(open))
// toggle 'is-open' class on panel button controls
this.#root.querySelector(`#${button.getAttribute('aria-controls')}`)
.classList.toggle('is-open', open)
// close others
this.#buttons.forEach((btn) => {
if (btn === button) return
btn.setAttribute('aria-expanded', 'false')
this.#root.querySelector(`#${btn.getAttribute('aria-controls')}`)
.classList.toggle('is-open', false)
})
})
})
}
}
// After page loads
document.querySelectorAll('[data-accordion]').forEach((el) => new Accordion(el));Accordion Using Details and Summary
Each section is a <details> element whose <summary> holds the section's heading. A heading inside the <summary> is the only way to combine a heading with <details>, because the <summary> has to be the first child of the <details> element. The browser toggles each section and carries its state in the open attribute, with no script or ARIA.
Giving every <details> element the same name value makes opening one section close the others, as the script in the ARIA example does.
The <details> element carries the group role, which doesn't create a landmark, and it accepts no other role, so a section built this way can't become a region as the panels in the ARIA example do.
The heading's text names the <summary>, but whether the heading is also reported as a heading, with a role and a level, varies between assistive technologies. That's why this entry recommends the ARIA construction for an accordion with headings.
Native accordion HTML
<h2>Popular Pets</h2>
<details name="pets">
<summary>
<h3>Cats</h3>
</summary>
<p>
A cat is a small, carnivorous mammal often adopted by a human family
because it is cute, does funny things, and purrs. They are often found
resting on laps, walking on laptops, or half-hiding around a corner
while peering at you.
</p>
</details>
<details name="pets">
<summary>
<h3>Dogs</h3>
</summary>
<p>
A dog is a vociferous omnivore descended from wolves that long ago
domesticated humans after coaxing them to share food. The size and
shape of dogs vary widely but all enjoy playing with tennis balls.
</p>
</details>Structure and Semantics
A section of an accordion with headings is two things. It's a disclosure, a button that shows and hides a panel, and the tests below set out what that takes. It's also a heading that wraps the button at a level fitting the page's outline. The button is the only thing inside the heading, and the panel follows the heading in the DOM.
A panel is a named region only when its content earns a landmark, as Panel Role describes.
Build the heading around a button rather than inside a <summary>. HTML allows a heading inside a <summary>, and that's the only way to combine a heading with a <details> element. The <summary> has to be the first child of <details>, so it can't sit inside a heading.
Support for a heading inside a <summary> is thin: its text still names the control, but whether it's also reported as a heading, with a role and a level, varies. Being reported as a heading is why the heading is there, so that construction falls short at the one thing it's for.
An accordion already built from <details> elements still has working disclosures. The recommendation is about where the heading goes. For the native construction, see Accordion Using Details and Summary.
Accordion Has Disclosures
Every section of an accordion is a disclosure first, with or without a heading. A section whose control doesn't report that it expands content leaves users with a list of titles and content they have no reason to look for.
Contains valid disclosures
Reported as: Accordion has no disclosures
Each section of an accordion must be a disclosure whose control reports that it expands content and whether that content is showing.
When the sections expand and collapse only visually, with plain elements as controls and no aria-expanded anywhere, screen reader users hear the section titles and nothing else. They can't tell that a section holds hidden content, that its title is operable, or which sections they've already opened.
Give each control the button role and an aria-expanded attribute that tracks its panel. A <details> element with a <summary> meets this too, as Accordion Using Details and Summary shows. Even so, a section's heading belongs around a button rather than inside a <summary>.
For what aria-expanded has to do, see aria-expanded.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Headings Structure
When a heading is needed, each section's heading wraps its button, and the button is the only thing inside it. Anything else that sits alongside a section's title, such as an icon, a badge, or a menu button for actions on the section, goes beside the heading rather than inside it.
Wraps controls in headings
Reported as: Accordion breaks heading hierarchy
In an accordion whose sections need headings, each section's button should be wrapped in a heading, and should be the only thing inside it.
When a section's button has no heading around it, the section's title is missing from the page's outline, and screen reader users can't move between the sections by heading. When the heading also holds other elements, such as a badge or a menu button, assistive technology reports their content as part of the section's title, so screen reader users hear it every time they reach the section.
Wrap each button in a heading and move anything else beside it. For a section built from <details> and <summary>, rebuild it as a heading wrapping a button rather than adding a heading inside the <summary>.
Severity: Minor
Headings Level
When a heading is needed, each section's heading carries a level that fits where the accordion sits in the page's outline, and every section in one accordion uses the same level. Count the level from the headings around the accordion rather than from the accordion alone: under an <h2>, the sections are <h3> elements. A native <h2> to <h6> element carries its level in the tag, while role="heading" needs aria-level alongside it.
Gives ARIA headings a level
Reported as: ARIA heading missing aria-level
A section heading built with role="heading" should declare its level with aria-level.
When the level is missing, the accordion's sections appear in the page's outline at a default depth nobody chose. Screen reader users who move through the page by heading get a structure that doesn't match how the content is organized.
Add aria-level set to the depth the sections sit at, one below the heading that introduces the accordion. Better, use a native heading element, which carries its level in the tag.
Severity: Moderate
Uses consistent heading levels
Reported as: Accordion has mixed heading hierarchy
Every section heading in one accordion should use the same level.
When the levels differ, the outline tells screen reader users that some sections are subsections of others, although the sections are peers. The mistake is invisible on screen, where the headings usually look identical.
Choose the level once for the whole accordion, to fit where it sits in the page's outline, and apply it to every section from one place in your code. If a section's content nests, put the nested headings inside its panel rather than varying the levels of the sections.
Severity: Minor
Role
An accordion section with a heading has two elements with one role each: the heading carries the heading role, and the button inside it carries the button role. Putting role="button" on the heading, or role="heading" on the button, merges the two elements the pattern keeps apart, and the section loses either its place in the outline or its control.
When a panel's content earns a landmark, the panel carries the region role too, as Panel Role and Accessibility Label describe.
Has the correct role
Reported as: Element has incorrect role
A section's button must carry the button role.
When the element users operate in the heading has no role, or the wrong one, assistive technology reports the section's title as plain text or as some other kind of element. Screen reader users can't tell that the title opens anything.
Use an HTML <button> inside the heading, which carries the role on its own, or set role="button" on the element users operate.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Applies role to the correct element
Reported as: Incorrect root element
Sanity check. Evinced checks that the button role sits on the selected element, the one users focus and operate in each section, rather than on one of its descendants. This check runs on every UI pattern, not only on accordions.
A section's button role should sit on the same element that receives focus and handles activation.
When the role sits on a descendant while a wrapper takes focus or handles the click, screen readers announce a button that isn't the element users operate, and keyboard users may reach a control that doesn't open the section.
Put the role, the accessible name, focusability, and the activation handling on that one element, and keep any wrapper free of roles and handlers. If the test selected a wrapper by mistake, select the section's button itself and run the test again.
Panel Role
A panel needs a role only when its content earns a landmark: content important enough that users will want to reach it directly and find it listed in a summary of the page. That's most often a panel holding headings of its own or a nested accordion.
When a panel earns one, make it a <section> element and name it with aria-labelledby pointing at its section's heading, as Accessibility Label describes. A named <section> has the region role implicitly, so it needs no role attribute.
A <details> element carries the group role, which isn't a landmark, and it accepts no other role. When a section built from <details> has content that earns a landmark, build it as a heading wrapping a button instead.
Gives panels a region role
Reported as: Accordion panel is not a section element
An accordion panel can be a named region, which makes it a landmark users can reach from the list of the page's landmarks.
When a panel holds substantial content, such as headings of its own or a nested accordion, a landmark lets users go straight to it. When a panel holds a few lines under its heading, a landmark only lengthens the list users have to read through.
Give the panel the region role only when its content earns one. Make it a <section> named by aria-labelledby pointing at its section's heading, and leave other panels without a role.
Severity: Minor
Button Name
Each section's button needs an accessible name, and that name is all users have to go on before they open anything, so it names the section rather than the act of opening it. In an accordion with headings, the heading's text is the button's text.
In a section built from a <details> element, the <summary> element is the control and takes its name from the text it contains.
A section's button takes its name from the text inside it. When its visible content is an icon rather than text, the name comes from the icon's alternative text, from an aria-labelledby pointing at visible text elsewhere, or from an aria-label when no such text is available.
Has an accessible name
Reported as: Accessible name
A section's button must have an accessible name that says which section it reveals.
When the button holds only an icon, a chevron, or an image with empty alt text, the section's title sits beside it, outside the button, and users get a control they can operate and can't identify. Every section's button then exposes the same name, so choosing between them means opening each one to find out what it was.
Name the button from the text inside it. When the design leaves no visible text to use, fall back to meaningful alt text on a child image, an aria-label, or an aria-labelledby pointing at text elsewhere on the page.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Puts the name in the summary text
Reported as: Summary has no accessible name
In a section built from a <details> element, the <summary> must contain text that says which section it reveals.
When the <summary> is empty, or holds only an icon or an image with empty alt text, users get a control they can operate and can't identify. Every section's summary then exposes the same name or none, so choosing between them means opening each one.
Put the text that names the section inside the <summary>. When the design leaves no visible text to use, give a child image meaningful alt text.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Details has summary with an accessible name
Reported as: Native disclosure is missing summary element
A section built from a native <details> element must have a <summary> element with an accessible name as its first child.
When the first child of the <details> element is the content itself, the browser still renders a control and falls back to a default string, usually "Details". Users get a stack of identically named controls and no way to tell which one holds what they came for.
Put a <summary> first inside each <details> element and give it the text that names the section. When the section needs a heading, build it as a heading wrapping a button instead.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Accessibility Label
When the panel carries a role, it should be labeled by its section's heading, whose text is also the button's text, so users who arrive at the panel can tell which section they're in. This is about the panel, not the button's own name.
Point the panel's aria-labelledby at the heading's id, or use aria-label when there's no heading text to point at. Point at the heading rather than the button: it carries the same text, and it's there before any script adds the button, as the example shows.
The attributes work only when the panel has a role that accepts a name, and in an accordion that's the region a <section> becomes once it's named, as Panel Role describes. Most panels don't earn a region, so most don't need a name. When a panel is a region, it needs a name, and these attributes supply it. The role and the name go together in both directions.
Names the panel from its heading
Reported as: Disclosure panel has no accessible name
An accordion panel that carries a role should take its accessible name from its section's heading.
When a panel has a region or group role and no name, assistive technology reports it as a region with nothing attached, or as an unnamed group it may skip. Users who move into the panel, or who arrive at it by jumping between regions, are reading a section with no indication of which part of the accordion it belongs to.
Give each section's heading an id, and point the panel's aria-labelledby at it. Give a panel a role only when its content earns one, and don't put the attribute on a panel with no role, where it names nothing.
Severity: Minor
aria-expanded
Users need to know whether a section's content is showing. This test covers whether each section reports its state and whether the report is right. Whether it changes when the section is operated belongs to Toggle Activation.
A section built from a <details> element reports its state through the open attribute. Neither <details> nor its <summary> takes aria-expanded, which would add a second report of the state with nothing keeping the two in step.
A section's button carries aria-expanded instead, in the collapsed state as well as the expanded one, with the value tracking whether its panel is showing.
Reflects aria-expanded state
Reported as: Conflicting aria-expanded value
The aria-expanded value on a section's button must match whether its panel is showing: false while the panel is hidden and true while it's showing.
When the value is derived from something other than the panel's real state, it reports the opposite of what's on screen: users skip a section that's open in front of them, or go looking for content that was never revealed.
Set the attribute from the same state that decides whether the panel renders.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 1.3.1 Info and Relationships (A) | Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. |
| EN 301 549 | 9.1.3.1 Info and Relationships | Same as the WCAG text. |
Updates aria-expanded
Reported as: Aria-expanded value did not change
The aria-expanded value on a section's button must change whenever its panel opens or closes.
When the panel changes and the value doesn't, the attribute goes on reporting the state the panel was in before, so screen reader users are told a section is closed while it's showing, or the reverse. In an accordion that closes other sections when one opens, the buttons of the closed sections need updating too.
Change the attribute in the same step that shows or hides the panel, whatever caused it to open or close.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 1.3.1 Info and Relationships (A) | Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. |
| EN 301 549 | 9.1.3.1 Info and Relationships | Same as the WCAG text. |
Has aria-expanded
Reported as: Missing aria-expanded attribute
A section's button must carry aria-expanded, in the collapsed state as well as the expanded one.
When the attribute is absent, nothing in the markup says the button reveals anything, so screen reader users have no reason to expect content to appear and can't tell afterward whether activating it worked.
Set it to false from the start rather than adding it once the panel has been opened.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 1.3.1 Info and Relationships (A) | Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. |
| EN 301 549 | 9.1.3.1 Info and Relationships | Same as the WCAG text. |
Disabled
An accordion's disabled state belongs on a section's button, never on its heading or its panel. Users can always collapse the section they've opened. An accordion that keeps one section open never disables that section's button to do it, although the APG accordion pattern allows aria-disabled there.
A section's button is disabled only when its content isn't available yet, the same rule as for any disclosure.
Disables only when role allows
Reported as: Component does not support disabled state
An accordion's disabled state must be set only on a section's button, never on its heading, its panel, or the accordion's container.
When disabled or aria-disabled is set on an element whose role has no disabled state, assistive technology may drop it or report the element as something it isn't, and the buttons inside stay fully operable. Nothing users can reach is actually disabled.
Take the attribute off the heading, panel, or container, and set it on the button of each section that is unavailable.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Disables only elements that support it
Reported as: Element type does not support disabled state
An element in an accordion must carry disabled or aria-disabled only when its role supports a disabled state.
When the state sits on an element whose role has none, such as a wrapper, a list item or a heading, assistive technology may drop it or report it on something users can't operate, while the control that really is unavailable is still reported as available.
Remove the attribute from that element, and set the disabled state on the control itself, using an element or role that supports it, such as a native <button>.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Avoids conflicting disabled states
Reported as: Conflicting disabled values
A section's button must declare its disabled state one way only.
When the button carries the disabled attribute alongside an aria-disabled value that disagrees with it, assistive technology may report a different state than the browser enforces, so users can't tell whether the section will open.
Use disabled on a native <button> and aria-disabled on a button built from another element, never both.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Uses aria-disabled rather than disabled
Reported as: Element does not support the disabled attribute
A section's button built from an element that doesn't support the disabled attribute must convey its disabled state with aria-disabled.
When disabled is set on such an element, browsers ignore it, and the button stays an available control in the accessibility tree however grayed out it looks. Screen reader users are told they can open a section the design means to be unavailable.
Set aria-disabled="true" instead, and keep it in step with the code that stops the button from toggling its panel. A native <button> avoids the problem, since disabled then does all of it.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Forbidden Interactive Children
A section's button holds the section's title and nothing users operate. Nested interactive elements create ambiguity for all users, who can't be certain which element will respond to their interaction.
Avoids nested interactives
Reported as: Forbidden interactive children; Nested interactive elements
A section's button must not contain or be nested inside another interactive element, such as a link or another button.
A button has no accessible children. An interactive element nested inside it is presentational, so screen reader users can't perceive or operate it as a control.
Move the interactive element out of the button. A control for actions on the section, such as a menu button, goes beside the heading rather than inside it.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Relationships
An accordion owns its sections, and they sit in the DOM in order inside the accordion's container: each heading, then its panel. Each section's button controls the panel that follows its heading, showing and hiding it.
aria-controls
As a best practice, each section's button should carry aria-controls naming its panel. The panel follows its heading directly, so users reach the content by reading on, and the attribute states the relationship for the assistive technology that uses it.
Controls a single target
Reported as: Aria-controls has multiple targets
A section's button should name only its own panel in aria-controls.
When the attribute lists several ids, such as every panel in an accordion where opening one section closes the others, the button no longer says which content it shows. Assistive technology that offers to move users to the controlled element has to pick one or offer none.
Point each button's aria-controls at its own panel. Closing the other sections is a side effect of opening this one, not something the button controls.
Severity: Minor
References valid aria-controls
Reported as: Invalid aria-controls reference
A section's button must point aria-controls at a panel that exists on the page.
When the id matches nothing, because it's misspelled or names a panel that hasn't been rendered, assistive technology finds no target and can't tell users what the button affects, while the markup still looks correct.
Generate each panel's id and its button's aria-controls value from the same source. When a panel exists only while it's open, add the attribute when the panel appears and remove it when the panel goes.
Severity: Minor
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 1.3.1 Info and Relationships (A) | Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. |
| EN 301 549 | 9.1.3.1 Info and Relationships | Same as the WCAG text. |
Sets aria-controls
Reported as: Missing aria-controls attribute
A section's button should name its panel with aria-controls.
When nothing connects the button to its panel, the relationship exists only in the layout and the script. Assistive technology that uses the attribute can't tell users which content the button shows, or offer to move them to it.
Give each panel a unique id, and set its button's aria-controls to that id.
Severity: Minor
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 1.3.1 Info and Relationships (A) | Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. |
| EN 301 549 | 9.1.3.1 Info and Relationships | Same as the WCAG text. |
aria-owns
An accordion holds its sections as DOM descendants of its container, each heading followed by its panel, in the order users meet them. That nesting states the whole structure, so an accordion has no reason to claim its sections with aria-owns.
Each section's button has no reason to own anything either: it reaches its panel through aria-controls.
Avoids aria-owns
Reported as: Avoid aria-owns
General check. Evinced checks that an accordion and its sections' buttons don't use aria-owns to claim headings, panels, or other elements. This check runs on every UI pattern, not only on accordions.
An accordion and its sections' buttons should not use aria-owns.
The headings and panels already sit inside the container in reading order. When an accordion or a section's button owns them anyway, assistive technology can present a panel away from its heading, and screen reader users meet content with no indication of which section it belongs to.
Remove aria-owns and keep each heading and its panel in the container, in order. A section's button names its panel with aria-controls, not aria-owns.
Owns only conforming roles
Reported as: Invalid aria-owns values
General check. Evinced checks that every element an accordion or a section's button claims with aria-owns has a role its owner accepts. This check runs on several UI patterns.
When an accordion or a section's button uses aria-owns anyway, each element it references should have a role its owner accepts.
An accordion's container has no role, so no owned element has a role it can accept, and a section's button treats everything inside it as presentational. When either claims headings, panels or other buttons, assistive technology can drop them or present them out of place, and screen reader users lose track of which panel belongs to which heading.
Remove aria-owns, and keep each heading and its panel inside the container in reading order, as the section intro describes.
Operation
Each section of an accordion is operated as a disclosure. With a pointer, clicking or tapping a section's button toggles its panel. With a keyboard, the Tab key moves focus to each button in turn, and Enter or Space toggles its panel. Focus stays on the button either way, and an open panel's content joins the tab order after its button.
An accordion's panels sit in the page flow and push the content below them down, so they don't close with Escape. Evinced checks for Escape only on panels that open over other content.
An accordion is a set of separate tab stops, one for each button, not a composite widget with a single tab stop. The arrow keys aren't part of the pattern, so don't add them to move between sections. Screen reader users can move between the sections by heading instead.
In an accordion that allows only one open section, opening a section closes the one that was open, and users can still collapse the open section directly.
Focus Sequence
Each section's button is a single tab stop, reachable with the Tab key whenever it's enabled, and opening a panel doesn't change that. Hide a closed panel with the hidden attribute or display: none rather than moving it off screen, so its content leaves the tab order with it and keyboard users don't land inside content nobody can see.
Reachable by keyboard
Reported as: Keyboard accessible
A section's button must be reachable with the Tab key.
When the button is built from markup that isn't focusable, such as a <div> or a <span> given click handling and a role, keyboard users can't reach it, so the section it reveals is out of their reach too.
Use a <button> element, which is focusable on its own, or a <summary> in a section built from <details>. When the button has to stay generic, add tabindex="0" so it joins the tab order in document order.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Skips tab order when disabled
Reported as: Disabled element focusable
A section's button that presents as disabled should not be a stop in the tab order.
When Tab still lands on a disabled button, because a native <button> was styled as disabled without the disabled attribute or a generic one kept tabindex="0", keyboard users arrive at a button that reveals nothing.
Use the disabled attribute on a native <button>, which removes it from the tab order. On a generic button, set tabindex="-1" while it's disabled and restore tabindex="0" when it's available again. If you keep it focusable so users can hear why it's unavailable, make sure assistive technology reports the disabled state and activation is blocked.
Severity: Minor
Puts focus on element with button role
Reported as: Focusable offset
The element that carries a section's button role must be the element that receives focus, not one of its descendants.
When the role sits on a wrapper and focus lands on a child inside it, screen reader users tabbing through the accordion hear nothing about what they've landed on, and can't tell it's a button that reveals a section.
Make the element with the role focusable, with a <button> element or tabindex="0", and remove focusability from the descendant that was taking focus.
Severity: Minor
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Keyboard Activation
A section's button activates with either the Enter or the Space key, as any button does. A native <button> responds to both keys on its own, and so does a <summary>. A button built from a generic element has to be given that behavior.
Operable by keyboard
Reported as: Button not activated by keyboard
A section's button must show and hide its panel when keyboard users activate it.
When a button built from a generic element handles only the click event, keyboard users can focus it but can't open the section.
Use a native <button> element, which is keyboard operable on its own. Otherwise, add keyboard event listeners that either toggle the panel or create a click event that does.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Ignores keyboard when disabled
Reported as: Disabled button keyboard activation
A section's button that reports itself as disabled should reject keyboard activation.
When a button carries aria-disabled="true" but the Enter or Space key still toggles its panel, screen reader users may be confused or lose trust in the interface.
Use a native <button>, which blocks interaction when disabled is set. Otherwise, check the disabled state in the keyboard handlers and return before toggling anything.
Severity: Serious
Activates with Enter and Space
Reported as: Button requires space key activation
A section's button must activate with the Space key as well as the Enter key, as a <button> element does.
When only Enter works, users who press Space, as they would on any other button, may conclude the button is unresponsive.
Add a Space keyup handler that toggles the panel, or use a native <button> element.
Severity: Minor
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Toggle Activation
Activating a section's button toggles its panel and the state the button reports, on every activation and by every route: a pointer click, the Enter key, and Space. A button that presents as disabled does neither.
Updates aria-expanded when operated
Reported as: Element's state did not change
A section's button must update aria-expanded every time it's operated.
Nothing looks broken to sighted users, who watch the panel open and close, while screen reader users are told it's still in the state it started in. After a few presses with no change in what they hear, they decide the button is broken.
Derive the attribute from the same state that decides whether the panel shows, and handle pointer and keyboard activation through one path rather than two.
Evinced reports this issue as Critical or as Serious, depending on the context where it shows up.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Returns to its earlier state
Reported as: Toggle state not resetting
Activating a section's button twice must return aria-expanded to the value it started with.
When the code assigns a fixed value on activation instead of inverting the current one, the attribute stops tracking the panel after the first press. The panel goes on opening and closing, and screen reader users are told a closed section is open.
Invert the state on each activation rather than assigning a fixed value.
Evinced reports this issue as Critical or as Serious, depending on the context where it shows up.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
When set, aria-expanded stays consistent with the open state
Reported as: Invalid toggle attribute
When a section built from a <details> element carries aria-expanded on the <details> or the <summary> element (not recommended), the value must stay consistent with the open attribute.
The browser derives the state from open while assistive technology reports the ARIA value to users, so the two disagree from the first toggle. Nothing in the native construction updates the attribute, so screen reader users go on being told that a section they've just opened is closed.
Remove aria-expanded and let the native state stand. When it can't be removed yet, update it from the toggle event on each <details> element so it at least tracks open.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Ignores activation when disabled
Reported as: Disabled element active
A section's button that presents as disabled must not toggle its panel when it's activated.
aria-disabled="true" changes only what's reported, not what the browser allows, so the click and key handlers keep firing unless the code checks the state itself. Users are told the section isn't available and then find that it opens, and they can't tell whether they've broken something they were warned off.
Check the disabled state at the top of the handler and return before changing anything, covering the pointer and the keyboard paths. On a native button, use the disabled attribute, which stops activation in the browser.
Severity: Moderate
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 1.3.1 Info and Relationships (A) | Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. |
| EN 301 549 | 9.1.3.1 Info and Relationships | Same as the WCAG text. |
Collapses its panel on a second press
Reported as: Unresponsive disclosure button
Activating an expanded section's button again must collapse its panel.
When the second activation does nothing, the panel stays open and the button gives no response. Users can't put the section away, and a button that works once and then stops reads as broken.
Handle every activation as a toggle: expand the panel when it's collapsed and collapse it when it's expanded, updating aria-expanded each time.
Severity: Minor
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Closes a panel without opening another
Reported as: Accordion panel does not close
Users should be able to collapse an expanded panel without expanding another, unless the accordion is designed to keep one panel open.
When a panel collapses only as another one opens, users can't return the accordion to its headings, and screen reader users hear a section stay open after they tried to close it.
Let a second activation of the same button collapse its panel. If the accordion is meant to keep one panel open, say so in visible instructions, and keep aria-expanded accurate on every button.
Severity: Minor
Event Listener Type
When script in an accordion responds to keys, it listens for keydown or keyup. The keypress event is deprecated, and browsers don't fire it for many keys, including the arrow keys, Home, End and Escape.
Avoids the keypress event
Reported as: Deprecated keypress listener
An accordion's key handling should use keydown or keyup, not keypress.
When a handler listens for keypress, keys the browser doesn't send through that event go unanswered, and keyboard users find the accordion ignoring keys it's meant to respond to.
Replace each keypress listener with a keydown listener, and read the key from event.key.
Severity: Minor
FAQ
Which WCAG success criteria do the accordion issues relate to?
Most accordion issues relate to 4.1.2 Name, Role, Value: a section's button with the button role and an accessible name, a <summary> first in each <details> element, sections that are valid disclosures, a disabled state set on the button and declared one way only, no interactive elements inside the button, a state that updates when the button is operated and stays consistent with open on a native section, and a button that collapses its panel when it's pressed again.
Under 1.3.1 Info and Relationships, each button's aria-expanded reflects whether its panel is showing, aria-controls is present and points at the panel, and a disabled button ignores activation. Under 2.1.1 Keyboard, each button can be reached with the Tab key, takes focus itself, and activates with Enter and Space.
The rest are best practices that map to no success criterion: giving ARIA headings a level, keeping heading levels consistent, wrapping each section's button in a heading, giving panels a region role only when their content earns a landmark, naming each panel from its heading, pointing aria-controls at a single panel, and keeping a disabled button out of the tab order and away from keyboard activation.
Why are some accordion issues marked as Critical and others as Minor?
Severity reflects how much an issue gets in users' way. Critical issues stop users outright: a section's button without the button role or a name, a section that can't be reached or opened by keyboard, or an aria-expanded value that's wrong or doesn't change when the button is operated.
Serious issues break the accordion for assistive technology users: a section that isn't a valid disclosure, a button with no aria-expanded or one whose value stays put when the panel opens or closes, a <details> element with no <summary>, a disabled state reported wrongly, an interactive element nested in a button, a stale aria-expanded on a native section, or a disabled button that still responds to the keyboard.
Moderate issues weaken navigation or trust, such as an ARIA heading with no level or a disabled button that still opens its section. Minor issues affect how easily users find their way, such as inconsistent heading levels, buttons without headings, panel roles and names, aria-controls that's missing or wrong, a disabled button left in the tab order, focus that lands inside a button, or a button that ignores the Space key.
Two checks carry no severity: the general check for aria-owns and the sanity check on where the button role sits.
Related Patterns
- Disclosure: the pattern each section follows, including its button's state and operation
Recommended Reading
- Accordion Pattern | W3C ARIA Authoring Practices Guide
- Disclosure (Show/Hide) Pattern | W3C ARIA Authoring Practices Guide
- heading role | W3C WAI-ARIA
- region role | W3C WAI-ARIA
<details>: The Details disclosure element | MDN<summary>: The Disclosure Summary element | MDN- The details and summary elements, again | Scott O'Hara