Disclosure Buttons and aria-expanded: What the Screen Reader Hears When Content Opens

November 15, 2026

A shopper tabs to a button labeled Shipping options and hears nothing about what happens next. The panel opens on click, the options appear below, and the screen reader user never learns they exist, because the button never said it controls anything.

A disclosure is a button plus a panel, and the state is the point

A disclosure is the simplest interactive pattern on the web and the one teams get wrong most often. It has two parts: a control that toggles, and a panel that appears and disappears. The W3C ARIA Authoring Practices Guide defines it that way, and the definition matters because the pattern is not about the visual effect. It is about whether the person using a keyboard and a screen reader can tell that hidden content exists, whether it is showing, and how to reach it.

The native construction is the HTML details element with a summary child. The browser does the toggling and carries the open and closed state in the open attribute, so nothing needs script or ARIA. A summary holding plain text is the well supported case, and it is the one to reach for first. The ARIA construction is a button carrying aria-expanded, together with the panel it reveals, and it takes script to show and hide the panel and keep the attribute in step with it. You build the ARIA version when the control has to be disabled, or needs a heading, or the panel has to carry a role of its own. What you do not do is build a div with a click handler and call it a disclosure, because then the screen reader announces a piece of text and the user has no control to activate at all.

The state attribute is doing real work here. A disclosure button with aria-expanded false tells the user there is hidden content and it is closed. Flip it to true and the user knows the content is showing. Leave the attribute off entirely and the user hears a button name and nothing else. They have no reason to press it, and if they do press it, the new content appears somewhere they were not told to look. That is the failure mode the aria-hidden trap shares: the markup describes a page the assistive technology cannot see.

One state per button, and the panel goes right after it

A button can report one kind of state, not two. When a button carries both aria-pressed and aria-expanded, assistive technology receives two state models for the same control, and screen readers may announce both, or only one, depending on the screen reader and browser. The Evinced knowledge base is blunt about this: if activating the button shows or hides content, it is a disclosure button, so remove aria-pressed. If activating it changes a state without showing additional content, it is a toggle button, so remove aria-expanded and aria-controls. The test is what the button does, not what the designer called it.

WCAG 2.2 success criterion 4.1.2, Name, Role, Value, at Level A, is the rule behind this. Every user interface component has to expose its name, role, and current state programmatically. A button that exposes two conflicting states does not meet that, because its programmatic state no longer reliably describes the control. The audit finding is usually filed as a name-role-value failure, and the fix is usually a one line deletion.

Placement matters as much as the attribute. Nothing about a button and a separate panel puts them in reading order, so that much has to be arranged. Keep the panel right after the button in the DOM, and point aria-controls at the panel id. Screen reader support for aria-controls varies, so the DOM order is the part you can rely on. A stack of related disclosures is an accordion, and when its sections have headings they share one level and fit the page heading outline, the same way tabs that announce their selected state keep their structure honest.

The test that takes a minute

Tab to the disclosure, listen for the name, the role, and the state. Activate it, and listen again: the state should flip from collapsed to expanded, and the panel content should be reachable with the next Tab press, not three screens down. If the announcement says nothing about expanded or collapsed, the attribute is missing or the element is not a button. If the state never changes, the script updates the class and forgot the attribute, which is the most common version of this bug in production.

Check the failure that hides from scanners: a panel that opens visually while aria-expanded stays false. The scanner sees the attribute present and moves on. The user hears collapsed, presses the button again to open what is already open, and the panel closes. That is a real defect with a real user cost, and it is invisible to every automated check. The only way to catch it is to listen.

Frequently asked questions

Does aria-expanded go on the button or on the panel?

On the button, the control that opens the content, never on the panel itself. A value of false tells users there is hidden content. Leave the attribute off controls that do not expand anything, because a false value on a plain button promises hidden content that does not exist.

Can I use a details and summary element instead of aria-expanded?

Yes, and it is usually the better choice. The browser handles the toggling and the open and closed state for you, with no script and no ARIA. Use the button and aria-expanded construction only when the control has to be disabled, needs a heading, or the panel needs a role of its own.

Why does my disclosure announce both pressed and expanded?

Because the button carries both aria-pressed and aria-expanded. A button may report one state model, not two. If it shows or hides content, keep aria-expanded and remove aria-pressed. If it only changes a state, keep aria-pressed and remove aria-expanded and aria-controls.

Do I need aria-controls on a disclosure button?

It is a safe default and the accordion pattern expects it, but screen reader support varies, so do not rely on it alone. Keep the panel immediately after the button in the DOM, because reading order is the mechanism that always works.