The HTML aria-expanded attribute
A control such as a <button> uses aria-expanded to tell assistive technology whether the content it opens is showing. The value is true when open and false when closed, and script has to flip it and show or hide the content as well.
Overview
Set on a control, aria-expanded says whether the content that control shows and hides is expanded or collapsed. It belongs on the focusable, interactive element that toggles the visibility of content in another element, so it goes on the button and not on the panel.
An element with nothing to expand should skip the attribute, since on most roles no value means it owns or controls nothing expandable, while false means collapsed content. So end nodes in a tree and rows with no child rows should leave it off.
Some HTML features report the state without ARIA. The open attribute of <details> is exposed on its <summary>, and a popovertarget button is exposed as expanded while its popover shows, with no attribute in the markup. Use them where they fit.
WAI-ARIA 1.2 narrowed the role list. It supports the state on application, button, checkbox, combobox, gridcell, link, listbox, menuitem, row, rowheader, tab and treeitem, plus inheritors such as switch.
The same version removed it from roles such as region, group, dialog and heading. Browsers drop it from the accessibility tree on those roles and on a plain <div>, so a panel marked this way reports nothing.
Syntax
<button aria-expanded="false" aria-controls="shipping">Shipping</button>
<div id="shipping" hidden>Orders leave within two business days.</div>
Values
| Value |
|---|
| true | false | undefined |
Example
<p style="margin:0 0 8px">Press a button to open or close its section.</p>
<div>
<button type="button" aria-expanded="false" aria-controls="shipping">Shipping</button>
<div id="shipping" style="padding:6px 0" hidden>Orders leave within two business days.</div>
</div>
<div style="margin-top:8px">
<button type="button" aria-expanded="false" aria-controls="returns">Returns</button>
<div id="returns" style="padding:6px 0" hidden>Send items back within 30 days for a refund.</div>
</div>
<p role="status" id="state" style="margin:10px 0 0">Both sections are closed.</p>
<script>
document.querySelectorAll('[aria-expanded]').forEach(function (button) {
button.addEventListener('click', function () {
const open = button.getAttribute('aria-expanded') !== 'true';
button.setAttribute('aria-expanded', String(open));
document.getElementById(button.getAttribute('aria-controls')).hidden = !open;
document.getElementById('state').textContent = button.textContent + ' is ' + (open ? 'open' : 'closed') + ', so its aria-expanded value is ' + open + '.';
});
});
</script>
Best practices
- Flip
aria-expandedand the content's visibility in one handler. - On a parent row in a
treegrid, set it on the row itself. A value on agridcellapplies to that cell alone and doesn't stand in for the row. - Keep it off a
rowheaderin a plain table. The state only applies to interactive elements, so a rowheader that descends from atableshould not use it. - Leave
role="button"off<summary>. That role is unnecessary there and can stop the element from correctly exposing its state.























