HTML Guides for descendant
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 id of 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.
The HTML specification defines a <label> as a caption for a specific form control. When you use the implicit labeling technique — wrapping a form control inside a <label> — the browser automatically associates the label text with that single control. If multiple labelable elements appear inside the same <label>, the association becomes ambiguous. Browsers may pick the first one, the last one, or behave inconsistently across implementations.
Labelable elements include <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>. Any combination of two or more of these inside a single <label> triggers this validation error.
This matters for accessibility. Screen readers rely on the label-to-control association to announce what a form field is for. When the association is ambiguous, assistive technology may announce the wrong label for a control, or skip one entirely, leaving users confused about what information to enter.
To fix this issue, split the <label> so that each one wraps exactly one form control, or use the for attribute to explicitly associate each label with a control by its id.
Examples
❌ Incorrect: multiple labelable elements inside one label
<label>
Name
<input type="text" name="name">
Age
<input type="number" name="age">
</label>
The single <label> contains two <input> elements, so the browser cannot determine which control the label text refers to.
✅ Correct: separate labels for each control
<label>
Name
<input type="text" name="name">
</label>
<label>
Age
<input type="number" name="age">
</label>
❌ Incorrect: mixing different labelable elements inside one label
<label>
Preferences
<select name="color">
<option>Red</option>
<option>Blue</option>
</select>
<textarea name="notes"></textarea>
</label>
✅ Correct: using explicit for attributes
<label for="color">Favorite color</label>
<select id="color" name="color">
<option>Red</option>
<option>Blue</option>
</select>
<label for="notes">Notes</label>
<textarea id="notes" name="notes"></textarea>
✅ Correct: single labelable descendant with implicit association
This is the pattern that works perfectly — one <label> wrapping exactly one control:
<label>
Age
<select name="age">
<option>Young</option>
<option>Old</option>
</select>
</label>
❌ Incorrect: hidden inputs don’t count, but other inputs do
Note that <input type="hidden"> is not a labelable element, so it won’t trigger this error on its own. However, combining a visible input with another labelable control still causes the issue:
<label>
Subscribe
<input type="checkbox" name="subscribe">
<button type="button">More info</button>
</label>
✅ Correct: separate each control into its own label
<label>
Subscribe
<input type="checkbox" name="subscribe">
</label>
<button type="button">More info</button>
In this case, the <button> doesn’t need a <label> at all — its text content serves as its accessible name. Only form controls that need a visible caption should be wrapped in a <label>.
The <label> element serves a specific purpose in HTML: it represents a caption for a form control. It can be associated with a control either through the for attribute (referencing the control’s id) or by wrapping the form control inside the <label> itself. Placing a <label> inside a <button> is semantically incorrect because a button is not a form control that benefits from labeling in this way — the button’s own text content or aria-label attribute already serves as its accessible name.
The HTML specification defines the content model of <button> as phrasing content, but explicitly excludes interactive content. Since <label> is classified as interactive content (it directs focus to its associated control when clicked), nesting it inside a <button> creates ambiguous behavior. When a user clicks the label, should it activate the button, or should it shift focus to the label’s associated control? Browsers handle this conflict inconsistently, which leads to unpredictable user experiences.
From an accessibility standpoint, this nesting is problematic because screen readers may announce the button in a confusing way, potentially reading it as containing a label for another element. The button’s accessible name should come directly from its text content, not from a nested <label>.
To fix this issue, simply replace the <label> with a <span> or plain text inside the button. If you need to style part of the button’s text differently, <span> elements are perfectly valid inside buttons.
Examples
❌ Invalid: <label> inside a <button>
<button type="submit">
<label>Submit Form</label>
</button>
❌ Invalid: <label> with a for attribute inside a <button>
<button type="button">
<label for="file-input">Choose File</label>
</button>
<input type="file" id="file-input">
✅ Fixed: Use plain text inside the <button>
<button type="submit">
Submit Form
</button>
✅ Fixed: Use a <span> for styling purposes
<button type="submit">
<span class="button-text">Submit Form</span>
</button>
✅ Fixed: Separate the <label> and <button>
If you need a label to describe a button’s purpose in a form, place the <label> outside the button and use the for attribute to associate it with a related input, or use aria-label on the button itself:
<label for="username">Username</label>
<input type="text" id="username">
<button type="submit" aria-label="Submit the username form">
Submit
</button>
The HTML specification defines the <label> element as a caption for a single form control. When you place multiple labelable elements inside one <label>, the browser cannot determine which control the label text is associated with. This creates ambiguity for assistive technologies like screen readers, which rely on a clear one-to-one relationship between labels and their controls to announce form fields correctly. It also breaks the click-to-focus behavior — clicking the label text should focus or activate the associated control, but with multiple controls nested inside, the intended target is unclear.
Labelable elements are specifically: <button>, <input> (except type="hidden"), <meter>, <output>, <progress>, <select>, and <textarea>. If any combination of two or more of these appears as descendants of a single <label>, the validator will flag the error.
A common scenario that triggers this is when developers try to group related fields — like a first name and last name — inside one label, or when they nest a button alongside an input within a label for styling convenience.
How to Fix It
- Use one <label> per control. Wrap each labelable element in its own <label>, or use the for attribute to associate a <label> with a specific control’s id.
- Use a container element for grouping. If you need to visually group related controls, use a <fieldset> with a <legend> instead of a single <label>.
Examples
❌ Incorrect: Two inputs inside one label
<label>
Name
<input type="text" name="first" placeholder="First">
<input type="text" name="last" placeholder="Last">
</label>
This is invalid because the <label> contains two <input> descendants.
✅ Fixed: Separate labels for each input
<label for="first">First name</label>
<input type="text" id="first" name="first">
<label for="last">Last name</label>
<input type="text" id="last" name="last">
✅ Fixed: Using a fieldset to group related controls
<fieldset>
<legend>Name</legend>
<label>
First
<input type="text" name="first">
</label>
<label>
Last
<input type="text" name="last">
</label>
</fieldset>
❌ Incorrect: A select and a button inside one label
<label>
Pick your age
<select name="age">
<option>Young</option>
<option>Old</option>
</select>
<button type="button">Help</button>
</label>
✅ Fixed: Button moved outside the label
<label>
Pick your age
<select name="age">
<option>Young</option>
<option>Old</option>
</select>
</label>
<button type="button">Help</button>
✅ Correct: One control inside a label (implicit association)
<label>
Age
<select id="age" name="age">
<option>Young</option>
<option>Old</option>
</select>
</label>
This is valid because the <label> contains exactly one labelable descendant — the <select> element. The association between the label text and the control is implicit and clear to both browsers and assistive technologies.
Ready to validate your sites?
Start your free trial today.