Disclosure
UI pattern
A disclosure has two parts: a control users activate to toggle the content, and a panel holding the content that appears and disappears.
Users expect the revealed content to follow the control directly in reading order: opening a disclosure should mean reading on, not going to look for whatever appeared. An attribute pointing at the panel is no substitute for placing it there.
The panel can sit in the page flow and push the content below it down, or it can open over other content and cover it, as a site navigation menu does.
What separates a disclosure from the patterns it resembles is that it never moves focus into the panel or claims a role for the content it reveals. A menu button opens a menu of commands, with its own role and arrow-key navigation. A button that opens a modal dialog moves focus into the content it opens and holds it there. Opening over the page doesn't set them apart.
There are two ways to build one, and both are dependable. The choice is about what each construction can do for you.
The native construction is the HTML <details> element with a <summary> child. The browser does the toggling and carries the open and closed state for you in the open attribute on <details>, so nothing here needs script or ARIA. Everything after the <summary> sits inside the <details> element, so the content follows the control in reading order without anyone arranging it.
What the construction doesn't give you is a disabled state, since HTML has no disabled attribute for <summary>.
A <summary> holding plain text is the well-supported case, and it's the one to reach for: its name, its role, and its expanded state all reach assistive technology.
That support thins out with something richer inside it, such as a heading, a link, or a button, because a control behaving as a button is meant to treat its children as presentational. A heading in particular isn't recommended there, for the reasons given under Structure and Semantics.
Test what you build against the environments you ship to. The published evidence on the elements is a few years old and thinner than the pattern's popularity suggests.
The ARIA construction is a button carrying an aria-expanded attribute, together with the panel it reveals. It takes script to show and hide the panel and to keep the attribute in step with it, and in exchange the button can be disabled. Nothing about a button and a separate panel puts them in reading order, so that much has to be arranged.
Build this construction when the control has to be disabled or needs a heading, or the panel has to carry a role of its own.
A stack of related disclosures is an accordion, with or without headings, and whether or not opening one section closes the others. See Accordion for building one, including when its sections need headings.
When to Use
Use a disclosure when:
- Content is useful to some readers but not all, such as extended detail, optional settings, or the answer to a frequently asked question.
- Showing everything at once would make the page hard to scan, and users benefit from choosing what to expand.
- The content being revealed is ordinary page content rather than a set of commands.
For using a disclosure to reveal navigation links, see Site Navigation. That entry carries its own worked example and requires disclosure controls that meet the requirements here.
Do not use a disclosure when:
- The revealed content is a menu of commands or actions. Use a menu button.
- The revealed content takes focus and holds it there. Use a button that opens a modal dialog. A panel that opens over other content without taking focus is still a disclosure.
- The content is a stack of related sections, with or without headings. Refer to Accordion, which also covers when the sections need headings.
- Every user needs the content. 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 place answers to frequently asked questions (FAQs) in disclosures. None of the disclosures have an associated heading. Refer to the Accordion UI pattern to learn how to associate a heading with a disclosure.
Disclosure Using Details and Summary
The native construction only needs HTML. Adding a name attribute with the same (arbitrary) value to the <details> elements enables an accordion-like behavior where opening one closes the others. Browsers that offer a reader mode also keep disclosures built with <details> and <summary> in it.
Native disclosure HTML
<h2>Frequently Asked Questions</h2>
<details name="faq">
<summary>
What is WCAG?
</summary>
<p>
The Web Content Accessibility Guidelines are an
international standard set by the W3C.
</p>
</details>
<details name="faq">
<summary>
What does POUR mean?
</summary>
<p>
The acronym <abbr>POUR</abbr> denotes the four WCAG principles:
Perceivable, Operable, Understandable and Robust.
</p>
</details>ARIA Disclosure Using a Button
The HTML structure should display the content by default so that it's available in browser reader mode and to users who have disabled JavaScript.
To provide a visual affordance, the button holds a disclosure triangle that rotates when the disclosure is opened. The button takes its accessible name from the <span> element its aria-labelledby references. A click event handler on the <span> forwards the click to the button. The disclosures here open and close independently of each other.
Before the JavaScript loads, the buttons are hidden and the panels are displayed. JavaScript then reveals the buttons and collapses the panels. Revealing the buttons shifts the layout when the script runs. CSS could prevent that, but this example keeps to its accessibility technique, naming an icon button. The build under Accordion › Examples shows one way to avoid the shift.
ARIA disclosure HTML
<h2>Frequently Asked Questions</h2>
<div class="aria-disclosure-summary">
<button
hidden
aria-labelledby="question-1"
aria-controls="answer-1"
aria-expanded="true">â–¶</button>
<span id="question-1">What is WCAG?</span>
</div>
<div id="answer-1">
The Web Content Accessibility Guidelines are an
international standard set by the W3C.
</div>
<div class="aria-disclosure-summary">
<button
hidden
aria-labelledby="question-2"
aria-controls="answer-2"
aria-expanded="true">â–¶</button>
<span id="question-2">What does POUR mean?</span>
</div>
<div id="answer-2">
The acronym <abbr>POUR</abbr> denotes the four WCAG
principles: Perceivable, Operable, Understandable and Robust.
</div>ARIA disclosure CSS
CSS rotates the disclosure triangle to match the button's state.
.aria-disclosure-summary > button[aria-expanded="true"] {
transform: rotate(0.25turn);
}JavaScript enables the disclosures and sets an initial state when it's loaded.
ARIA disclosure JavaScript
class AriaDisclosure {
#control
#panel
constructor(control) {
this.#control = control
// reveal the button
this.#control.removeAttribute('hidden')
// collapse the panel
this.#control.setAttribute('aria-expanded', 'false')
// find the panel
this.#panel = document.getElementById(
this.#control.getAttribute('aria-controls')
)
// button click toggles the state
this.#control.addEventListener('click', () => {
this.expanded = !this.expanded
})
// forward button name clicks to the button
const btnName = document.getElementById(
this.#control.getAttribute('aria-labelledby')
)
btnName.addEventListener('click', () => {
this.#control.click()
})
}
get expanded() {
return this.#control.getAttribute('aria-expanded') === 'true'
}
set expanded(isExpanded) {
this.#control.setAttribute('aria-expanded', String(isExpanded))
this.#panel.hidden = !isExpanded
}
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll(".aria-disclosure-summary > button[aria-controls]")
.forEach((control) => {
const disclosure = new AriaDisclosure(control)
disclosure.expanded = false
})
})Structure and Semantics
Users need to identify a disclosure's control, know what content it reveals, and perceive whether that content is currently showing. Both constructions convey all three; they differ in what does the conveying.
The native construction uses the elements themselves. The <summary> element is the control, the text inside it names the section, and the open attribute on the <details> element carries the state. No attribute in the table below belongs on either element.
A heading inside the <summary> isn't recommended, even though HTML allows it. Its text still becomes the control's accessible name, so the name is never at risk. What varies between assistive technologies is whether that text is also reported as a heading, with a role and a level. In some it isn't.
The heading can't go around the <summary> instead, because the <summary> has to be the first child of the <details> element. A control that needs a heading, whether it stands alone or sits in a stack, is built as a heading wrapping a button, and Accordion shows the technique.
The marker the browser draws on a <summary> deserves the same care. It isn't only decoration. In some browser and screen reader pairings it's how the open and closed state is conveyed, so removing it with list-style: none and summary::-webkit-details-marker { display: none } to make room for a custom affordance can take the only state cue with it. Style the marker rather than deleting it, or satisfy yourself that the state still reaches users without it.
The ARIA construction uses attributes, because a button and a separate panel have no structure that states any of this. The button carries the button role (supplied by the <button> element, or declared on a generic one), along with an accessible name that identifies the section, aria-expanded to report the state, and, as a best practice, aria-controls to name the panel.
| Attribute | How to use |
|---|---|
aria-expanded | ARIA construction. Set on the button to true while the panel is visible and false while it's hidden. Leave it off both the <details> and <summary> elements, whose state is already carried by open on the <details> element. |
aria-controls | ARIA construction. Set on the button to the id of the panel it shows and hides, as a best practice. |
aria-labelledby | ARIA construction. Set on the panel only when the panel carries a role, chiefly when a heading is associated with the disclosure, to name it from the control that reveals it. |
aria-label | ARIA construction. Names a panel that carries a role directly, when there's no control text to point aria-labelledby at. |
aria-describedby | ARIA construction. Set on a disabled button to point at the explanation of why the content isn't available yet. |
Role
Users have to be able to tell that a disclosure's control is a control and that it expands something, and each construction reaches that result by a different route.
No ARIA role describes what the <details> and <summary> elements are. Their semantics do reach assistive technology, though not uniformly. Setting a role on either isn't recommended: it replaces the native disclosure semantics with whatever was assigned and takes the heading support described under Structure and Semantics with it, so a heading inside the <summary> stops being reported as a heading anywhere. Leave both elements without a role, and build the ARIA construction when the pattern needs semantics they can't provide.
An ARIA disclosure's control carries the button role, which the HTML <button> element supplies on its own and a generic element has to declare with role="button".
A disclosure's panel carries a role only when its content warrants one, content important enough that users will want to reach it directly from an overview of the page. Most panels don't.
Button has the correct role
Reported as: Element has incorrect role
An ARIA disclosure's control must carry the button role.
When the control is a generic element with click handling and no role, or carries a different role, assistive technology reports it as plain content or as the wrong kind of control. Users can't tell that it does anything, let alone that it reveals content.
Build the control from a <button> element, which supplies the role on its own, or set role="button" on the element when it has to stay generic.
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 the button 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, rather than on one of its descendants. This check runs on every UI pattern, not only on disclosures.
A disclosure button's 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 toggle the panel.
Put the role, the accessible name, aria-expanded, 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 button itself and run the test again.
Button Name
A disclosure's control 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 a native disclosure, the <summary> element is the control and takes its name from the text it contains.
In an ARIA disclosure the control is a button, and the name comes 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 disclosure 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 wording that names the section usually sits in the panel below it, and users get a control they can operate and can't identify. In a stack of sections, every 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
A <summary> element 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. In a stack of sections, every 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 disclosure 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 set of identically named controls and no way to tell which one holds what they came for. Nothing on screen signals the problem, because whatever heading the design intended is still visible, just not connected to the control.
Put a <summary> first inside the <details> element and give it the text that names the section.
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
The content a disclosure reveals is captioned by the control that reveals it. A <summary> captions the content inside its <details> element with nothing added.
A button's panel carries a role only when its content warrants one, which is chiefly when a heading is associated with the disclosure. Whenever the panel carries a role, it needs a label, from an aria-labelledby pointing at the control or an aria-label. On a panel with no role, a label is ignored.
Names the panel from its control
Reported as: Disclosure panel has no accessible name
A disclosure panel that carries a role should take its accessible name from the control that reveals it.
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 content, or reach it by jumping between regions, can't tell which control produced it.
Give the button an id and point the panel's aria-labelledby at it, so the name can't drift from the button's wording. Give the panel a role only when its content earns one, and leave the attribute off a panel with no role, where it names nothing.
Severity: Minor
aria-expanded
Users need to know whether the content is showing, and the two constructions report that differently. This test covers whether the report is there and whether it's right. Whether it changes when the control is operated belongs to Toggle Activation.
The native construction reports the state through the open attribute on the <details> element. Neither that element nor its <summary> takes aria-expanded, which would add a second report of the state with nothing keeping the two in step.
A button used as a disclosure control carries aria-expanded instead, in the collapsed state as well as the expanded one, with the value tracking whether the content is available.
Reflects aria-expanded state
Reported as: Conflicting aria-expanded value
A disclosure button's aria-expanded value must match whether its panel is showing: false while the content is hidden and true while it's available.
When the value is derived from something other than the content's real state, it reports the opposite of what's on screen: users skip content 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
A disclosure button's aria-expanded value 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 the content is hidden while it's showing, or the reverse.
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 disclosure 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
A disclosure whose control is unavailable has to say so, and only one of the two constructions can.
The native construction has no disabled state. HTML gives <summary> no disabled attribute, and adding ARIA to the element is the wrong fix, so a disclosure whose control has to be disabled is built the other way.
An ARIA disclosure's button conveys the state the way any button does, through the disabled attribute on a native <button> and aria-disabled="true" on a control built from a generic element.
A disclosure control should seldom be disabled. When the button construction is used, it may be, but generally only when the content isn't available yet because some condition hasn't been satisfied: data that hasn't arrived, a required step in a workflow that isn't finished, or a request in flight that has to complete before the content can be shown.
In those cases say why, and point the button's aria-describedby at the explanation. Never disable a disclosure to keep sensitive information out of reach, and never to stop users from closing a panel they've opened.
Avoids conflicting disabled states
Reported as: Conflicting disabled values
A disclosure button must declare its disabled state one way only.
When a native <button> carries the disabled attribute alongside aria-disabled="false", or the two otherwise disagree, assistive technology can report a different state from the one the browser enforces. Users can't tell whether the button will reveal anything.
Use the disabled attribute on a native <button> and aria-disabled on a button built from a generic 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 disclosure button built from a generic element must convey its disabled state with aria-disabled rather than disabled.
When the disabled attribute is set on a <div> or a <span>, browsers ignore it, so the button is still reported as available and still opens its panel, however grayed out it looks. Users are told they can act on something the design means to be unavailable.
Set aria-disabled="true" instead, and keep it in step with the code that blocks activation. When a native <button> would do, switch to one and use disabled.
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 a disclosure 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. |
Forbidden Interactive Children
A disclosure's control, whether a <summary> or a button, holds its name 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 disclosure's control must not contain or be nested inside another interactive element, such as a link or a button.
A control that behaves as 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 control, into the panel or next to the control.
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
A disclosure's control and its panel are two elements with one job between them, and the markup has to say so.
The native construction states the relationship structurally. The <summary> element is the first child of <details>, and everything after it is the panel, so the browser establishes the connection without an attribute.
In an ARIA disclosure the two are usually siblings, so nothing about the DOM connects them. The button should carry aria-controls set to the panel's id.
Under either construction, a disclosure has no reason to use aria-owns.
aria-controls
A disclosure button should carry aria-controls naming its panel, as a best practice. In most disclosures the panel directly follows the button, so users reach the content by reading on, and the attribute states the relationship for the assistive technology that uses it.
When the panel isn't adjacent, the attribute matters more but probably isn't sufficient. Users should be able to reach the revealed content without having to search for it, and the attribute alone can't provide that because assistive technology support for offering to move users to the element it names is thin. The aria-controls attribute documents the relationship; it doesn't deliver access to the content.
Controls a single target
Reported as: Aria-controls has multiple targets
A disclosure control's aria-controls attribute should name one element.
When it lists several ids, the control claims to drive several separate elements at once, and the attribute stops answering the one question it exists to answer. Assistive technology that offers to move users to the controlled content has to pick a target or offer none.
Point aria-controls at the single element holding the panel's content. When several elements appear together, wrap them in one container and name that container instead.
Severity: Minor
Controls an element on the page
Reported as: Invalid aria-controls reference
A disclosure control's aria-controls attribute must name an element that exists on the page.
When the id matches nothing in the document, the reference fails quietly. Assistive technology finds no target, so it can't tell users what the control affects, and the markup still looks correct to anyone reading it.
Generate the panel's id and the aria-controls value from one source. When the panel exists only while it's open, add the attribute when the panel appears and remove it when the panel is destroyed.
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 disclosure button should identify its panel with aria-controls.
When the attribute is missing, nothing in the markup states which content the button affects, and assistive technology that uses the relationship has nothing to offer users. When the panel doesn't directly follow the button, the only cue left is a visual one: seeing which region changed.
Add aria-controls to the button and set its value to the panel's unique id. When the panel isn't adjacent, move it next to the button rather than relying on the attribute to lead users there.
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
A disclosure's control and its panel are related by control rather than by ownership, and aria-controls already states that relationship. Neither construction has a reason to reach for aria-owns.
Avoids aria-owns
Reported as: Avoid aria-owns
General check. Evinced checks that a disclosure's button doesn't use aria-owns to claim its panel or other elements as its children. This check runs on every UI pattern, not only on disclosures.
A disclosure button should not use aria-owns.
When the button owns its panel, the panel becomes one of the button's children, and a button's children are presentational. Assistive technology users perceive the panel's content only as part of the button's name and can't operate anything interactive inside it.
Remove aria-owns and keep the panel outside the button. Reference the panel with aria-controls, which already states the relationship.
Operation
Users operate a disclosure by activating its control, and the panel appears or disappears. With a pointer, clicking or tapping the control toggles the panel. With a keyboard, the Tab key moves focus to the control and either Enter or Space activates it. Focus stays on the control either way.
The panel's own content joins the tab order while the panel is open and leaves it when the panel is hidden, so the control is one tab stop and anything interactive inside the panel supplies the stops after it.
A native disclosure does all of this for you. The browser toggles the open attribute on <details> and updates the state it exposes, with no script involved.
An ARIA disclosure built on a native button inherits the keyboard behavior and needs script only to show and hide the panel and to update aria-expanded. For a control built from a generic element, which has to reproduce that behavior in script, refer to Button › Operation.
A disclosure whose panel opens over other content also closes when users press Escape.
Focus Sequence
A disclosure's control is a single tab stop, reachable with the Tab key whenever it's enabled. Revealing a panel doesn't change that. Hide the 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 disclosure's control must be reachable with the Tab key.
When the control 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 content it reveals is out of their reach too.
Use a <summary> or a <button> element, both focusable on their own. 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 disclosure 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 control 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 disclosure button's 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 page hear nothing about what they've landed on, and can't tell it's a control that reveals content.
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 disclosure's control activates with either the Enter or the Space key, as any button does. A <summary> responds to both keys with no script, and so does a native <button>. A button built from a generic element has to be given that behavior.
Operable by keyboard
Reported as: Button not activated by keyboard
A disclosure 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 reveal the content it controls.
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 disclosure 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 disclosure 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 control 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 disclosure's control toggles the panel and the state the control reports, on every activation and by every route: a pointer click, the Enter key, and Space. A control that presents as disabled does neither.
Updates aria-expanded when operated
Reported as: Element's state did not change
A disclosure 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 control 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 disclosure 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 panel 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. |
Summary aria-expanded stays consistent with the open state
Reported as: Invalid toggle attribute
When a native disclosure carries aria-expanded on either 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. The native construction has no script doing the toggling, so nothing updates the attribute, and screen reader users go on being told that a panel 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 the <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 disclosure 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 button isn't available and then find that it works, and they can't tell whether the change took effect or 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. |
Escape Key
A disclosure whose panel opens over other content, as a site navigation menu does, closes when users press Escape, and focus returns to the control that opened it. A panel that sits in the page flow and pushes the content below it down covers nothing, so it doesn't need this. Neither construction supplies the behavior: a native <details> element doesn't close on Escape either, so the requirement applies to both.
Closes on Escape, restoring focus
Reported as: Disclosure is not closed with Escape key
A disclosure panel that opens over other content must close when users press Escape, and focus must return to the control that opened it.
When there's no handler for Escape, the panel covers what users were reading and the only way out is to find the control again and activate it. The partial version is as common: the panel closes, focus is left on an element that no longer exists and drops to the document body, and screen reader users hear nothing to say where they are.
Listen for Escape on the control and the panel, so the key works from either, and close the panel and call focus() on the control in the same step. Closing means clearing open on a <details> element, or hiding the panel and setting aria-expanded to false on a button.
Severity: Serious
| 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. |
Event Listener Type
When script in a disclosure 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
A disclosure'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 disclosure 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 disclosure issues relate to?
Most disclosure issues relate to 1.3.1 Info and Relationships and 4.1.2 Name, Role, Value. Under 1.3.1, the button's aria-expanded state is present and reflects whether the panel is open, aria-controls references the panel, and a disabled control ignores activation.
Under 4.1.2, the control has an accessible name and holds no interactive elements, the button has the button role, a native disclosure starts with a <summary>, the disabled state is declared one way only, and activating the control updates its state.
Under 2.1.1 Keyboard, the control can be reached with the Tab key, takes focus itself, and activates with Enter and Space, and a panel that opens over other content closes with the Escape key.
Four criteria map to no WCAG success criterion and are best practices: aria-controls references a single target, a disabled button leaves the tab order and ignores the keyboard, and a panel that carries a role takes its name from its control.
Why are some disclosure issues marked as Critical and others as Minor?
Severity reflects how much an issue gets in users' way. Critical issues leave users unable to tell what the control does or whether the content is shown, or unable to use it: a control with no name, no button role, or no way to reach or activate it by keyboard, or an aria-expanded value that's wrong or doesn't change when the control is operated.
Serious issues mislead or strand users, such as a button with no aria-expanded or one whose value stays put when the panel opens or closes, a stale aria-expanded on the native elements, a <details> element with no <summary>, a disabled state the browser and assistive technology disagree on, an interactive element nested in the control, a disabled button that still responds to the keyboard, or an Escape key that doesn't close the disclosure and return focus.
The Moderate issue is a disabled control that still opens. Minor issues cost users a little extra effort but leave the content reachable, such as a missing or broken aria-controls, a disabled button that's still a tab stop, focus that lands inside the control rather than on it, a panel role with no name, 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
- Menu Button: a button that opens a menu of commands, with its own role and arrow-key navigation
- Dialog: content that takes focus and holds it, usually opened by a button
- Accordion: a stack of disclosures, usually with a heading for each section
- Site Navigation: uses a disclosure to reveal navigation links
- Button: the control an ARIA disclosure is built on
Recommended Reading
- Disclosure (Show/Hide) Pattern | W3C ARIA Authoring Practices Guide
- button role | W3C WAI-ARIA
aria-expandedstate | W3C WAI-ARIA<details>: The Details disclosure element | MDN<summary>: The Disclosure Summary element | MDN<button>: The Button element | MDN- Disclosure Widgets | Adrian Roselli
- The details and summary elements, again | Scott O'Hara