HTML Guides for aria-activedescendant
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-activedescendant attribute tells assistive technologies which child element within a composite widget — such as a combobox, listbox, or autocomplete dropdown — is currently "active" or focused. Instead of moving actual DOM focus to each option, the parent element (like an input) retains focus while aria-activedescendant points to the visually highlighted option by referencing its id. This allows screen readers to announce the active option without disrupting keyboard interaction on the input.
When aria-activedescendant is set to an empty string (""), it creates an invalid state. The HTML and ARIA specifications require that any ID reference attribute either contains a valid, non-empty ID token or is omitted altogether. An empty string is not a valid ID, so the W3C validator flags this as an error: Bad value "" for attribute "aria-activedescendant" on element "input": An ID must not be the empty string.
This problem commonly occurs in JavaScript-driven widgets where aria-activedescendant is cleared by setting it to "" when no option is highlighted — for example, when a dropdown closes or the user clears their selection. While the developer's intent is correct (indicating that nothing is active), the implementation is wrong.
Why this matters
- Accessibility: Screen readers may behave unpredictably when encountering an empty ID reference. Some may silently ignore it, while others may announce errors or fail to convey widget state correctly.
- Standards compliance: The ARIA specification explicitly requires ID reference values to be non-empty strings that match an existing element's
id. - Browser consistency: Browsers handle invalid ARIA attributes inconsistently, which can lead to different experiences across platforms and assistive technologies.
How to fix it
- Remove the attribute when no descendant is active. Use
removeAttribute('aria-activedescendant')in JavaScript instead of setting it to an empty string. - Set a valid ID when a descendant becomes active, pointing to the
idof the currently highlighted or selected option. - Never render the attribute in HTML with an empty value. If your framework or templating engine conditionally renders attributes, ensure it omits the attribute entirely rather than outputting
aria-activedescendant="".
Examples
Incorrect: empty string value
This triggers the W3C validation error because the attribute value is an empty string.
<input type="text" role="combobox" aria-activedescendant="" />
Correct: attribute omitted when no option is active
When nothing is active, simply leave the attribute off.
<input type="text" role="combobox" aria-expanded="false" />
Correct: valid ID reference when an option is active
When a user highlights an option, set aria-activedescendant to that option's id.
<div role="combobox">
<input
type="text"
role="combobox"
aria-expanded="true"
aria-controls="suggestions"
aria-activedescendant="option2" />
<ul id="suggestions" role="listbox">
<li id="option1" role="option">Apple</li>
<li id="option2" role="option" aria-selected="true">Banana</li>
<li id="option3" role="option">Cherry</li>
</ul>
</div>
Correct: managing the attribute dynamically with JavaScript
The key fix in JavaScript is using removeAttribute instead of setting the value to an empty string.
<div role="combobox">
<input
id="search"
type="text"
role="combobox"
aria-expanded="true"
aria-controls="results" />
<ul id="results" role="listbox">
<li id="result1" role="option">First result</li>
<li id="result2" role="option">Second result</li>
</ul>
</div>
<script>
const input = document.getElementById('search');
function setActiveOption(optionId) {
if (optionId) {
input.setAttribute('aria-activedescendant', optionId);
} else {
// Remove the attribute instead of setting it to ""
input.removeAttribute('aria-activedescendant');
}
}
</script>
In summary, always ensure aria-activedescendant either points to a real, non-empty id or is removed from the element. Never set it to an empty string.
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>
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