HTML Guides for aria-expanded
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The aria-expanded attribute communicates to assistive technologies whether a related grouping element (such as a dropdown menu, accordion panel, or collapsible section) is currently expanded or collapsed. It accepts only three valid values:
"true"— the controlled element is expanded and visible."false"— the controlled element is collapsed and hidden."undefined"— the element has no expandable relationship (this is also the implicit default when the attribute is omitted entirely).
This validation error typically occurs when the attribute is accidentally set to a non-boolean value. A common mistake is writing aria-expanded="aria-expanded", which mimics the old HTML4 pattern for boolean attributes like checked="checked". However, aria-expanded is not a standard HTML boolean attribute — it is an ARIA state attribute that requires an explicit string value of "true" or "false".
Setting an invalid value means assistive technologies like screen readers cannot correctly interpret the state of the control. A screen reader user may not know whether a menu is open or closed, leading to a confusing and inaccessible experience. Browsers may also handle the invalid value unpredictably, potentially treating it as truthy or ignoring it altogether.
How to fix it
- Identify the element with the invalid
aria-expandedvalue. - Replace the value with
"true"if the associated content is currently expanded, or"false"if it is collapsed. - If the button has no expand/collapse relationship at all, remove the
aria-expandedattribute entirely. - Ensure that JavaScript toggling logic updates the attribute to
"true"or"false"— never to any other string.
Examples
❌ Invalid: attribute set to a non-boolean string
<button aria-expanded="aria-expanded" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu">
<li>Option 1</li>
<li>Option 2</li>
</ul>
The value "aria-expanded" is not a recognized value and triggers the validation error.
✅ Fixed: attribute set to "false" (collapsed state)
<button aria-expanded="false" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu" hidden>
<li>Option 1</li>
<li>Option 2</li>
</ul>
✅ Fixed: attribute set to "true" (expanded state)
<button aria-expanded="true" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu">
<li>Option 1</li>
<li>Option 2</li>
</ul>
❌ Invalid: other common incorrect values
<!-- Using "yes" instead of "true" -->
<button aria-expanded="yes">Details</button>
<!-- Using "1" instead of "true" -->
<button aria-expanded="1">Details</button>
<!-- Empty value -->
<button aria-expanded="">Details</button>
All of these are invalid. The only accepted values are "true", "false", and "undefined".
✅ Toggling with JavaScript
When toggling aria-expanded dynamically, make sure the value is always set to the correct string:
<button aria-expanded="false" aria-controls="panel" onclick="togglePanel(this)">
Show details
</button>
<div id="panel" hidden>
<p>Additional details here.</p>
</div>
<script>
function togglePanel(button) {
const expanded = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!expanded));
const panel = document.getElementById(button.getAttribute("aria-controls"));
panel.hidden = expanded;
}
</script>
This ensures the attribute always toggles between "true" and "false", keeping the markup valid and the experience accessible for all users.
The aria-expanded attribute only accepts the values "true", "false", or "undefined", and it belongs on interactive elements, not on generic div elements.
The aria-expanded attribute indicates whether a grouping element controlled by the current element is expanded or collapsed. Valid values are "true" (the controlled element is expanded), "false" (the controlled element is collapsed), and "undefined" (the element does not own or control a groupable element). Any other value, such as "yes", "no", "0", "1", or an empty string, is invalid.
Beyond the attribute value itself, aria-expanded is meant for interactive elements like button, a (with href), or elements with an appropriate ARIA role such as role="button". A plain div has no implicit interactivity, so placing aria-expanded on it without an interactive role triggers a validation warning.
To fix this, use a valid value and place the attribute on an appropriate element. If you must use a div, give it an interactive role and make it keyboard accessible with tabindex.
Examples
Invalid usage
<div aria-expanded="yes">
<p>Panel content</p>
</div>
Two problems here: the value "yes" is not valid, and a plain div is not an interactive element.
Valid usage
<button aria-expanded="false" aria-controls="panel1">
Toggle panel
</button>
<div id="panel1" hidden>
<p>Panel content</p>
</div>
The aria-expanded="false" attribute sits on a button, which is interactive by default, and the value is one of the allowed strings. When the panel opens, update the attribute to "true" and remove the hidden attribute via JavaScript.
An <a> element without an href attribute can't use aria-expanded unless it also has a role that supports that state.
Without href, an <a> isn't a link. HTML treats it as a placeholder, and its ARIA role is generic. The aria-expanded attribute only applies to certain roles, such as button, link, tab and menuitem, so on a placeholder anchor it describes a state that screen readers have nothing to attach to. The anchor also can't receive keyboard focus, so keyboard users can't reach the control.
This usually comes from a menu or dropdown toggle written as <a aria-expanded="false"> and wired up with JavaScript. The message also lists aria-checked and aria-level, but adding either of them doesn't fix the element: the validator then asks for href or role instead. Add an href if the element takes the user somewhere. If it opens and closes part of the page, replace it with a <button type="button">, which is focusable, works with the keyboard and supports aria-expanded without extra attributes. Keeping the <a> with role="button" and tabindex="0" also passes validation, but then your script has to handle the Enter and Space keys.
Invalid example
<a class="menu-toggle" aria-expanded="false" aria-controls="account-menu">Account</a>
<ul id="account-menu" hidden>
<li><a href="/profile">Profile</a></li>
<li><a href="/logout">Log out</a></li>
</ul>
Valid example
<button type="button" class="menu-toggle" aria-expanded="false" aria-controls="account-menu">Account</button>
<ul id="account-menu" hidden>
<li><a href="/profile">Profile</a></li>
<li><a href="/logout">Log out</a></li>
</ul>
A <button> with role="combobox" needs an aria-expanded attribute, and the validator reports this message when the button doesn't have one.
Custom select menus often use a <button> as a combobox. Clicking it opens a popup, usually an element with role="listbox", where the user picks a value. ARIA requires aria-expanded on every combobox so screen readers can announce whether that popup is open. Start with false in the markup, then have your script set it to true when the list opens and back to false when it closes.
The message also lists type, but type on its own won't clear it: add only type="button" and the validator reports that aria-expanded is required instead. Keep type="button" anyway, since a <button> inside a form submits that form by default.
A button that already has aria-expanded can still trigger this message if it also carries an aria-activedescendant written with no value, as in <button aria-activedescendant role="combobox" aria-expanded="false">. Depending on the order of the attributes, the validator reports this message instead of flagging the empty value. The value of aria-activedescendant has to be the id of the option that currently has focus, so leave the attribute out of the markup until an option is active.
Invalid example
<button role="combobox" aria-controls="sort-options" aria-label="Sort by">
Newest first
</button>
<ul id="sort-options" role="listbox" hidden>
<li id="sort-newest" role="option" aria-selected="true">Newest first</li>
<li id="sort-oldest" role="option" aria-selected="false">Oldest first</li>
</ul>
Valid example
<button type="button" role="combobox" aria-expanded="false" aria-controls="sort-options" aria-label="Sort by">
Newest first
</button>
<ul id="sort-options" role="listbox" hidden>
<li id="sort-newest" role="option" aria-selected="true">Newest first</li>
<li id="sort-oldest" role="option" aria-selected="false">Oldest first</li>
</ul>
The aria-required attribute tells assistive technologies that a form field must be filled in before the form can be submitted. However, this attribute is only valid on elements that function as interactive widgets. A bare div has no implicit ARIA role, so assistive technologies have no context for what kind of input is expected. The validator flags this because an aria-required attribute on a generic div is effectively meaningless without additional ARIA attributes that define the element's role and behavior.
This matters for several reasons:
- Accessibility: Screen readers and other assistive technologies rely on roles to understand how to present an element to users. Without a role,
aria-requiredprovides incomplete information. - Standards compliance: The WAI-ARIA specification defines which attributes are allowed on which roles. Using
aria-requiredwithout an established role violates these constraints. - Browser behavior: Browsers may ignore or misinterpret ARIA attributes when they appear on elements that lack the proper role context.
How to fix it
Option 1: Use native semantic HTML (preferred)
Whenever possible, use native HTML form elements. They come with built-in accessibility semantics, keyboard interaction, and validation — no ARIA needed.
Replace a div with aria-required="true" with an appropriate form control using the native required attribute:
<input type="text" required>
<select required>
<option value="">Choose one</option>
<option value="1">Option 1</option>
</select>
<textarea required></textarea>
Option 2: Add an appropriate role attribute
When you must use a div as a custom widget (styled and enhanced with CSS and JavaScript), add the correct role attribute to give it semantic meaning. Choose the role that matches the widget's actual behavior — don't just pick one arbitrarily.
Common roles that support aria-required:
combobox— a custom dropdown with text inputlistbox— a custom selection listradiogroup— a group of radio-like optionsspinbutton— a numeric stepper (also requiresaria-valuemax,aria-valuemin, andaria-valuenow)textbox— a custom text input
Option 3: Add other qualifying ARIA attributes
For certain widgets like sliders or spinbuttons, you may need aria-valuemax, aria-valuemin, and aria-valuenow in addition to (or as part of) defining the role. These attributes inherently establish a widget context.
Examples
❌ Invalid: aria-required on a plain div
<div aria-required="true">
<div data-value="One">1</div>
<div data-value="Two">2</div>
<div data-value="Three">3</div>
</div>
This triggers the validation error because the div has no role or other ARIA attributes to define what kind of widget it is.
✅ Fixed: Adding the appropriate role
<div aria-required="true" role="radiogroup" aria-label="Pick a number">
<div role="radio" aria-checked="false" tabindex="0">1</div>
<div role="radio" aria-checked="false" tabindex="0">2</div>
<div role="radio" aria-checked="false" tabindex="0">3</div>
</div>
Adding role="radiogroup" gives the div a semantic identity. Note that child elements also need appropriate roles and attributes for the widget to be fully accessible.
✅ Fixed: Using native HTML instead
<fieldset>
<legend>Pick a number</legend>
<label><input type="radio" name="number" value="1" required> 1</label>
<label><input type="radio" name="number" value="2"> 2</label>
<label><input type="radio" name="number" value="3"> 3</label>
</fieldset>
This approach uses native radio buttons with the required attribute, eliminating the need for ARIA entirely. The browser handles accessibility, keyboard navigation, and form validation automatically.
✅ Fixed: A custom spinbutton with value attributes
<div role="spinbutton"
aria-required="true"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
aria-label="Quantity"
tabindex="0">
50
</div>
For a spinbutton role, you must also provide aria-valuemin, aria-valuemax, and aria-valuenow to fully describe the widget's state.
A <label> can't carry aria-expanded unless it also has a role, and the validator reports this message when it finds one that doesn't.
aria-expanded tells assistive technologies whether a control's collapsible content, such as an accordion panel or a menu, is open or closed. ARIA allows it only on interactive roles like button, combobox, link or tab. A <label> has no ARIA role of its own, since its only job is to name a form control. The message lists three attributes, but only role clears it. Add aria-checked or aria-level and the validator asks for role instead.
The message usually comes from an accordion whose clickable header is a <label>, often with aria-controls pointing at the panel and a script that toggles it. Keyboard users can't reach that header, because a <label> doesn't take focus, and screen readers don't announce it as something to press. Make the header a <button type="button"> and move aria-expanded and aria-controls onto it. If the section has a title, put the button inside a heading. A <button> has no for attribute, so drop it. For a simple disclosure that needs no script, <details> and <summary> track the open state without any ARIA.
Adding role="button" and tabindex="0" to the <label> also clears the message. You then have to handle the Enter and Space keys in script, though, and a <label> linked to a form control keeps forwarding its clicks to that control. That link causes a second error too: the validator reports that aria-expanded and aria-controls must not be used on a <label> associated with a form control. A <label> that wraps its control gets a similar message about a label that is an ancestor of a labelable element.
Invalid example
<label id="shipping-header" aria-expanded="false" aria-controls="shipping-panel">
Shipping
</label>
<div id="shipping-panel" role="region" aria-labelledby="shipping-header" hidden>
<p>Orders ship within two business days.</p>
</div>
Valid example
<h2>
<button type="button" id="shipping-header" aria-expanded="false" aria-controls="shipping-panel">
Shipping
</button>
</h2>
<div id="shipping-panel" role="region" aria-labelledby="shipping-header" hidden>
<p>Orders ship within two business days.</p>
</div>
A <p> element has an aria-expanded attribute, but a paragraph has no role that supports it.
aria-expanded tells assistive technologies whether the content an element controls, such as an accordion panel or a menu, is shown or hidden. ARIA allows it on roles like button, link, combobox, tab and treeitem. A <p> has the paragraph role, which has no expanded state, so the attribute means nothing there. The message lists three attributes, but only role clears it. Add aria-checked or aria-level and the validator asks for role alone.
The attribute often ends up on the paragraph that holds a toggle's text, or on the content a script opens and closes. If the paragraph doesn't show or hide anything, delete the attribute. If clicking it does, put a <button type="button"> inside the paragraph and move aria-expanded and aria-controls onto the button. Keyboard users can't reach a clickable <p>, because it doesn't take focus, and screen readers don't announce it as something to press.
Adding role="button" and tabindex="0" to the <p> also clears the message, but then the Enter and Space keys have to be handled in script. A <button> gets focus and keyboard activation without any script.
Invalid example
<p aria-expanded="false" aria-controls="hours">Opening hours</p>
<div id="hours" hidden>
<p>Monday to Friday, 9:00 to 17:00.</p>
</div>
Valid example
<p>
<button type="button" aria-expanded="false" aria-controls="hours">Opening hours</button>
</p>
<div id="hours" hidden>
<p>Monday to Friday, 9:00 to 17:00.</p>
</div>
The span element is a generic inline container with no inherent semantics. On its own, it carries no meaning for assistive technologies. When you add ARIA attributes like aria-expanded or aria-valuenow to a span, you are signaling that the element represents an interactive widget — but the validator (and assistive technologies) need more context. Many ARIA attributes are only permitted on elements that have certain roles, and some roles require a specific set of attributes to function correctly.
For example, aria-valuenow is designed for range widgets like sliders and progress bars. According to the WAI-ARIA specification, if you use aria-valuenow, the element must also have aria-valuemin, aria-valuemax, and a role such as progressbar, slider, meter, or scrollbar. Similarly, aria-expanded is meant for elements with roles like button, combobox, link, or treeitem. Placing these attributes on a bare span without the corresponding role violates the ARIA rules and triggers this validation error.
This matters for several reasons:
- Accessibility: Screen readers rely on the
roleto determine how to present a widget to users. Without it, ARIA state attributes become meaningless or confusing. - Standards compliance: The HTML specification integrates ARIA rules, and validators enforce that ARIA attributes are used in valid combinations.
- Browser behavior: Browsers use the
roleto build the accessibility tree. Aspanwitharia-valuenowbut norolemay be ignored or misrepresented to assistive technology users.
How to fix it
- Add the correct
roleto thespan, along with all attributes required by that role. - Use a semantic HTML element instead of a
spanwhen one exists (e.g.,<progress>or<button>). - Remove unnecessary ARIA attributes if the
spanis purely decorative or the attributes were added by mistake.
If your span is purely visual (e.g., a decorative asterisk for required fields), don't add state-related ARIA attributes to it. Instead, use aria-hidden="true" to hide it from assistive technologies, and place ARIA attributes on the actual form control.
Examples
Incorrect: aria-expanded on a span without a role
<span aria-expanded="false">Menu</span>
The validator reports the missing role because aria-expanded isn't valid on a generic span.
Correct: Add a role (or use a button)
<span role="button" tabindex="0" aria-expanded="false">Menu</span>
Or, better yet, use a real button element:
<button aria-expanded="false">Menu</button>
Incorrect: aria-valuenow without the full set of range attributes and role
<span class="progress-indicator" aria-valuenow="50">50%</span>
Correct: Include the role and all required range attributes
<span role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
50%
</span>
Or use the native <progress> element, which has built-in semantics:
<progress value="50" max="100">50%</progress>
Incorrect: aria-required on a decorative span
<label for="email">
<span class="required" aria-required="true">*</span>
</label>
<input id="email" name="email" type="email">
The aria-required attribute belongs on the form control, not on the decorative asterisk.
Correct: Hide the decorative indicator and mark the input as required
<label for="email">
<span class="required" aria-hidden="true">*</span>
</label>
<input id="email" name="email" type="email" aria-required="true">
If you also want screen readers to announce "required" as part of the label text, add visually hidden text:
<label for="email">
<span aria-hidden="true">*</span>
<span class="visually-hidden">required</span>
</label>
<input id="email" name="email" type="email" required>
The key takeaway: whenever you use ARIA state or property attributes on a span, make sure the element also has the correct role and all companion attributes required by that role. When a native HTML element already provides the semantics you need — such as <button>, <progress>, or <meter> — prefer it over a span with ARIA, as native elements are more robust and require less additional markup.
A <label> whose for attribute points at a form control can't carry aria-expanded, because the label only names that control and has no open or closed state to report.
aria-expanded tells assistive technologies whether the content a control shows and hides, such as a dropdown menu or an accordion panel, is open right now. Screen readers read it out when the control gets focus, as in "Menu, button, collapsed". ARIA permits it on interactive roles like button, checkbox, combobox, link and tab.
Assistive technologies don't expose a <label> linked by for as an element in its own right. They read its text as the control's accessible name, and focus goes to the control, so a state set on the label is never announced. A <label> that wraps its control instead of using for gets a separate message, about a label that is an ancestor of a labelable element. The fix is the same.
The message usually comes from a CSS-only toggle: a hidden checkbox opens a mobile menu, or hidden radio buttons switch slides or tabs, and the <label> is styled as the button. CSS can't change attributes, so a hard-coded aria-expanded="false" stays false after the menu opens. Moving it onto the checkbox clears the error but leaves the value just as stale. Use a <button type="button"> with aria-expanded and aria-controls instead, with a short script that flips the value and shows or hides the content. If you'd rather avoid script, <details> and <summary> report their open state to assistive technologies with no ARIA at all.
For a slider or tabs built from radio buttons, remove aria-expanded. The checked radio already tells screen readers which slide is selected, and the validator rejects aria-expanded on <input type="radio"> unless the input also has a role.
Invalid example
<nav>
<input type="checkbox" id="menu-toggle" class="menu-toggle">
<label for="menu-toggle" aria-expanded="false">Menu</label>
<ul class="menu">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
Valid example
<nav>
<button type="button" id="menu-button" aria-expanded="false" aria-controls="main-menu">
Menu
</button>
<ul id="main-menu" hidden>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<script>
const button = document.getElementById("menu-button");
const menu = document.getElementById("main-menu");
button.addEventListener("click", () => {
const open = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!open));
menu.hidden = open;
});
</script>
The aria-expanded attribute is redundant on a summary element when it is the direct child of a details element, because the browser already communicates the expanded/collapsed state through the native open attribute on details.
The summary element, when used as the first child of a details element, acts as the built-in toggle control. Assistive technologies already understand this relationship and automatically convey whether the disclosure widget is open or closed based on the details element's open attribute. Adding aria-expanded to summary in this context creates duplicate semantics, which can confuse screen readers by announcing the state twice.
If you're using JavaScript to toggle aria-expanded manually, you can safely remove it and rely on the native behavior instead. The details/summary pattern is one of the best examples of built-in accessibility that requires no extra ARIA attributes.
Incorrect Example
<details>
<summary aria-expanded="false">More information</summary>
<p>Here are the additional details.</p>
</details>
Correct Example
<details>
<summary>More information</summary>
<p>Here are the additional details.</p>
</details>
If you need the section to be open by default, use the open attribute on the details element:
<details open>
<summary>More information</summary>
<p>Here are the additional details.</p>
</details>
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries