HTML Guides for label
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.
Every <optgroup> needs a name: give it a label attribute, or make a <legend> element its first child.
Browsers show the group name as a heading above the group's options, and screen readers announce it when the user moves into the group. The HTML specification requires the label attribute whenever the <optgroup> has no child <legend>. Without either one, the options sit under a blank heading and screen readers have nothing to announce.
Use the label attribute unless you have a reason not to. Every browser supports it. The validator only checks that the attribute exists, so label="" passes even though the group still has no name. Write the real group name.
The <legend> child comes from customizable select, a recent addition to HTML that lets you mark up and style the group heading. Browsers without customizable select support ignore the <legend> tag and show the group without a name. Older versions of the W3C validator reject it as well: they report the <legend> as a stray start tag and still ask for label. If you use a <legend>, keep the label attribute too. Browsers that support customizable select take the name from the <legend>, and older ones fall back to the attribute.
Invalid example
<label for="car">Car</label>
<select id="car" name="car">
<optgroup>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</optgroup>
<optgroup>
<option value="audi">Audi</option>
<option value="bmw">BMW</option>
</optgroup>
</select>
Valid example
<label for="car">Car</label>
<select id="car" name="car">
<optgroup label="Swedish cars">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</optgroup>
<optgroup label="German cars">
<option value="audi">Audi</option>
<option value="bmw">BMW</option>
</optgroup>
</select>
Valid example with a legend
<label for="car">Car</label>
<select id="car" name="car">
<optgroup label="Swedish cars">
<legend>Swedish cars</legend>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</optgroup>
</select>
The label element represents a caption for a form control. There are two ways to associate a label with its control:
- Implicit association — Place the form control directly inside the
labelelement. Noforattribute is needed because the browser automatically pairs the label with the nested control. - Explicit association — Use the
forattribute on thelabel, setting its value to theidof the target form control. The control doesn't need to be nested inside thelabelin this case.
Both methods are valid on their own. The problem occurs when you combine them incorrectly: you nest an input inside a label that has a for attribute, but the input either has no id or has an id that doesn't match the for value. This creates a contradiction — the for attribute points to a specific id, yet the nested input doesn't fulfill that reference. Browsers may handle this inconsistently, and assistive technologies like screen readers could fail to announce the label correctly, harming accessibility.
Why this matters
- Accessibility: Screen readers rely on the
for/idpairing to announce labels for form controls. A mismatched or missingidcan leave the control unlabeled for users who depend on assistive technology. - Standards compliance: The HTML specification requires that when a
forattribute is present, it must reference theidof a labelable element. A mismatch violates this rule. - Browser behavior: Some browsers will fall back to the implicit association when
fordoesn't resolve, but others may prioritize the broken explicit association, leaving the control effectively unlabeled.
How to fix it
You have two options:
- Remove the
forattribute if theinputis already nested inside thelabel. The implicit association is sufficient on its own. - Add or correct the
idon the nestedinputso it matches theforattribute value exactly.
Examples
❌ Nested input with no matching id
The for attribute says "email", but the input has no id at all:
<label for="email">
<input type="email" name="email">
</label>
❌ Nested input with a mismatched id
The for attribute says "email", but the input's id is "user-email":
<label for="email">
<input type="email" name="email" id="user-email">
</label>
✅ Fix by removing the for attribute (implicit association)
Since the input is nested inside the label, the association is automatic:
<label>
<input type="email" name="email">
</label>
✅ Fix by adding a matching id (explicit association)
The for value and the id value are identical:
<label for="email">
<input type="email" name="email" id="email">
</label>
✅ Fix by using explicit association without nesting
If you prefer to keep the for attribute, the input doesn't need to be nested at all:
<label for="email">Email</label>
<input type="email" name="email" id="email">
In most cases, choosing either implicit or explicit association — rather than mixing both — is the simplest way to avoid this error. If you do combine them, just make sure the for value and the id value match exactly.
The label element associates a caption with a form control. There are two ways to create this association:
- Implicit association — Place the form control directly inside the
labelelement. Nofororidattributes are needed. - Explicit association — Use the
forattribute on thelabel, setting its value to theidof the associated form control.
Both methods work independently. The problem arises when you mix them incorrectly — nesting a select inside a label that has a for attribute, but the select either has no id or has an id that doesn't match the for value. In this situation, the explicit association (via for) points to nothing or to the wrong element, while the implicit association (via nesting) still exists. This contradictory state violates the HTML specification and can cause assistive technologies like screen readers to misidentify or skip the label, reducing accessibility for users who rely on them.
The WHATWG HTML specification requires that when a for attribute is present on a label, it must reference a valid labelable element by id. If the form control is already nested inside the label, the for attribute is redundant — but if present, it still must correctly reference that control.
How to Fix
You have two options:
- Remove the
forattribute — If theselectis already inside thelabel, implicit association handles everything. This is the simplest fix. - Add or correct the
id— Keep theforattribute but ensure theselecthas a matchingid.
Examples
❌ Incorrect: for attribute with no matching id
The for attribute references "age", but the select has no id at all:
<label for="age">
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
❌ Incorrect: for attribute with a mismatched id
The for attribute references "age", but the select has a different id:
<label for="age">
Age
<select id="age-select">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 1: Remove the for attribute (implicit association)
Since the select is nested inside the label, implicit association is sufficient:
<label>
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 2: Match the for attribute with the id (explicit association)
Keep the for attribute and give the select a matching id:
<label for="age">
Age
<select id="age">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 3: Separate the label and select (explicit association only)
If you prefer to keep the select outside the label, explicit association with matching for and id is required:
<label for="age">Age</label>
<select id="age">
<option>Young</option>
<option>Old</option>
</select>
In most cases, option 1 (removing the for attribute) is the cleanest solution when the control is already nested. Use explicit association when the label and control are in separate parts of the DOM, such as in complex table or grid layouts.
A label element with a for attribute contains a form control whose id is missing or doesn't match the for value.
A label can be tied to its control in two ways: by wrapping the control, or by naming the control's id in for. When for is present, it alone picks the labeled control, and the label may not contain any other labelable element. The labelable elements are button, input (except type="hidden"), meter, output, progress, select and textarea, and the message names the one that broke the rule.
The browser follows for and ignores the nested control. If for points at an id that doesn't exist, the label has no control at all: clicking its text does nothing, and the control inside can end up without an accessible name.
A button usually gets here through a search form that wraps both the search field and its submit button in one <label for="search-field">. The field's id matches, but the button can't share it because every id in a page must be unique. The validator also reports that the label holds more than one labelable descendant, and screen readers may read the button's text as part of the field's name.
Close the label before the button, or move the button out of it, so the label holds only the control it names. When the label wraps a single control, either give that control an id equal to the for value, or remove for and let the wrapping make the association.
Invalid example
<form role="search" action="/search">
<label for="search-field">
Search for:
<input type="search" id="search-field" name="q">
<button type="submit">Search</button>
</label>
</form>
Valid example
<form role="search" action="/search">
<label for="search-field">
Search for:
<input type="search" id="search-field" name="q">
</label>
<button type="submit">Search</button>
</form>
The for attribute on a <label> element tells the browser which form control the label describes. When a user clicks or taps the label, the browser transfers focus to the associated control. For this to work, the value of for must exactly match the id of a form element such as an <input>, <textarea>, <select>, or <button>.
An empty string ("") is not a valid ID according to the HTML specification. The WHATWG HTML standard requires that an id attribute value must contain at least one character and must not contain ASCII whitespace. Since no element can have an empty-string id, a <label> with for="" can never successfully reference anything, making it both invalid markup and a broken association.
Why this matters
Accessibility: Screen readers rely on the for/id pairing to announce what a form control is for. A label with an empty for attribute creates no programmatic association, meaning assistive technology users may not know what a field is asking for. This directly impacts WCAG compliance.
Usability: A properly associated label expands the clickable area of its form control. For example, clicking a label associated with a checkbox will toggle the checkbox. An empty for attribute breaks this behavior.
Standards compliance: The W3C validator flags this because it violates the HTML specification. Keeping markup valid helps ensure consistent behavior across browsers and future-proofs your code.
How to fix
You have three options:
- Set
forto a validid: Give the associated form control a uniqueidand reference it in the label'sforattribute. - Remove
forand use implicit association: Wrap the form control inside the<label>element. This creates an implicit association without needingfororidat all. - Remove the
forattribute: If the label is purely decorative or not meant to be associated with a control, simply remove the emptyforattribute.
Examples
❌ Empty for attribute (triggers the error)
<label for="">Username:</label>
<input type="text" name="username">
The label has no meaningful association with the input because for="" is not a valid reference.
✅ Fix: Use a valid for/id pair
<label for="username">Username:</label>
<input type="text" id="username" name="username">
The for="username" now matches id="username" on the input, creating an explicit association.
✅ Fix: Use implicit association by nesting
<label>
Username:
<input type="text" name="username">
</label>
Wrapping the input inside the <label> creates an implicit association. No for or id attributes are needed.
❌ Multiple labels with empty for attributes
<form>
<label for="">Email:</label>
<input type="email" name="email">
<label for="">Subscribe to newsletter</label>
<input type="checkbox" name="subscribe">
</form>
✅ Fixed with proper associations
<form>
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<label for="subscribe">Subscribe to newsletter</label>
<input type="checkbox" id="subscribe" name="subscribe">
</form>
Each id must be unique within the document, and each for attribute must reference exactly one id. If your labels are generated by a framework or CMS with empty for values, check the template or component configuration to ensure proper id values are being output.
The <label> element represents a caption for a form control. When you use the for attribute, its value must match the id of the form control it labels. According to the HTML specification, an id attribute value must not contain any whitespace characters — this includes spaces, tabs, line feeds, carriage returns, and form feeds. Since for must reference a valid ID, the same restriction applies to its value.
This error typically occurs when a developer uses a space-separated name (like "user name" or "first name") instead of a single continuous token. Browsers may fail to establish the programmatic association between the label and its form control when the for value contains whitespace. This directly impacts accessibility: screen readers rely on this association to announce the label text when a user focuses on the input. It also breaks the click-to-focus behavior where clicking a label moves focus to its associated control.
To fix this issue, replace any whitespace in the for attribute value with a valid character such as a hyphen (-), underscore (_), or camelCase. Make sure the id on the corresponding form control matches exactly.
Examples
Incorrect — whitespace in the for attribute
<form>
<label for="user name">Name</label>
<input type="text" id="user name">
</form>
The value "user name" contains a space, which makes it an invalid ID reference. The validator will report: Bad value "user name" for attribute "for" on element "label": An ID must not contain whitespace.
Correct — using an underscore instead of a space
<form>
<label for="user_name">Name</label>
<input type="text" id="user_name">
</form>
Correct — using a hyphen instead of a space
<form>
<label for="user-name">Name</label>
<input type="text" id="user-name">
</form>
Correct — using camelCase
<form>
<label for="userName">Name</label>
<input type="text" id="userName">
</form>
Incorrect — leading or trailing whitespace
Whitespace doesn't have to be in the middle of the value to trigger this error. Leading or trailing spaces also make the ID invalid:
<form>
<label for=" email">Email</label>
<input type="text" id=" email">
</form>
This can be easy to miss when values are generated dynamically or copied from another source. Trim the value to fix it:
<form>
<label for="email">Email</label>
<input type="text" id="email">
</form>
Alternative — wrapping the input inside the label
If you wrap the form control inside the <label> element, you don't need the for attribute at all. The association is implicit:
<form>
<label>
Name
<input type="text">
</label>
</form>
This approach avoids potential ID mismatches entirely, though explicit for/id pairing is often preferred for flexibility in layout and styling.
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>
The <option> element represents a choice within a <select> dropdown, a <datalist>, or an <optgroup>. According to the HTML specification, every option must have a name — the text that is displayed to the user. This name can come from one of two sources: the text content between the opening <option> and closing </option> tags, or the label attribute on the element. If neither is provided, the option renders as a blank entry in the dropdown, which creates several problems.
From an accessibility standpoint, screen readers rely on the option's label to announce each choice to the user. An empty, unlabeled option gives assistive technology nothing meaningful to read, making the control unusable for some users. From a usability perspective, sighted users see a blank line in the dropdown and have no idea what it represents. And from a standards compliance perspective, the HTML specification explicitly requires that an <option> without a label attribute must not have empty content.
Note that the value attribute is separate from the display text. The value is what gets submitted with the form, while the label/text content is what the user sees. Setting a value does not satisfy the requirement for visible text.
How to fix it
You have two options:
- Add text content inside the
<option>element (the most common and recommended approach). - Add a
labelattribute to the<option>element. When present, thelabelattribute takes precedence over the text content for display purposes in many browsers.
If you intend for an option to serve as a placeholder (e.g., "Choose one…"), make sure it still has visible text content.
Examples
❌ Empty option without a label
This triggers the validation error because the <option> elements have no text content and no label attribute:
<select name="size">
<option value=""></option>
<option value="s"></option>
<option value="m"></option>
<option value="l"></option>
</select>
✅ Fix: Add text content inside each option
<select name="size">
<option value="">Choose a size</option>
<option value="s">Small</option>
<option value="m">Medium</option>
<option value="l">Large</option>
</select>
✅ Fix: Use the label attribute
The label attribute provides the display text. The element content can then be empty or differ from the label:
<select name="size">
<option value="" label="Choose a size"></option>
<option value="s" label="Small"></option>
<option value="m" label="Medium"></option>
<option value="l" label="Large"></option>
</select>
✅ Mixing both approaches
You can use text content for some options and the label attribute for others. You can even use both on the same element — in that case, the label attribute typically takes precedence for display:
<select name="size">
<option value="">Choose a size</option>
<option value="s">Small</option>
<option value="m" label="Medium">Medium (M)</option>
<option value="l" label="Large">Large (L)</option>
</select>
❌ Common mistake: Assuming value counts as a label
This is still invalid because value is not displayed to the user:
<select name="color">
<option value="red"></option>
</select>
✅ Corrected
<select name="color">
<option value="red">Red</option>
</select>
In almost all cases, placing readable text directly inside the <option> tags is the simplest and most compatible approach. Reserve the label attribute for situations where you need the displayed text to differ from the element's content.
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>.
A <label> that points at a form control with its for attribute can't carry aria-controls, because that relationship belongs on the control itself.
aria-controls takes the id of the element whose content or visibility the current element changes, such as the panel a tab shows or the slide a carousel control brings into view. Screen readers report that relationship on the focused element, and some let users jump from there to the controlled region.
Once a <label> is associated with a labelable element (<input>, <select>, <textarea>, <button> and a few others), it has no role of its own. Its text becomes the control's accessible name, and a click on it goes to the control. Keyboard and screen reader users interact with the control, not with the label, so aria-controls on the label sits on an element they never reach.
The error often comes from CSS-only tabs or sliders built from radio buttons, where each <label> is styled as the visible trigger and the <input> is hidden. Put aria-controls on each <input> and leave the <label> with just its for attribute. If you hide the radio buttons, use a visually hidden technique rather than display: none, so they stay focusable and screen readers can still announce them.
Invalid example
<input type="radio" name="slides" id="slide-1" checked>
<label for="slide-1" aria-controls="panel-1">First slide</label>
<input type="radio" name="slides" id="slide-2">
<label for="slide-2" aria-controls="panel-2">Second slide</label>
<div id="panel-1">First slide content</div>
<div id="panel-2">Second slide content</div>
Valid example
<input type="radio" name="slides" id="slide-1" aria-controls="panel-1" checked>
<label for="slide-1">First slide</label>
<input type="radio" name="slides" id="slide-2" aria-controls="panel-2">
<label for="slide-2">Second slide</label>
<div id="panel-1">First slide content</div>
<div id="panel-2">Second slide content</div>
The aria-describedby attribute is not allowed on a <label> element when that <label> contains a labelable element (such as <input>, <select>, <textarea>, or <button>).
aria-describedby points assistive technology at one or more elements that describe a control, referenced by their id. The description belongs on the control being described, not on its label. When a <label> wraps the control, the label already supplies the accessible name, so adding a description there leaves it unclear what the text is meant to describe.
That ambiguity is why the spec rejects the attribute on a <label> that is an ancestor of a labelable element. A "labelable element" is any element a <label> can be associated with: <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>.
Move aria-describedby onto the form control so the description is announced for the element it actually refers to.
Examples
Invalid: aria-describedby on a label that wraps an input
<label aria-describedby="password-hint">
Password
<input type="password" name="password">
</label>
<span id="password-hint">Use at least 8 characters.</span>
Fixed: move it to the input
<label>
Password
<input type="password" name="password" aria-describedby="password-hint">
</label>
<span id="password-hint">Use at least 8 characters.</span>
The aria-errormessage attribute is not allowed on a <label> element when that <label> contains a labelable element (such as <input>, <select>, <textarea>, or <button>).
aria-errormessage identifies the element that holds the error text for a control whose value is invalid. It belongs on the control itself, alongside aria-invalid="true", and its value is the id of the element containing the message. A <label> has no validity state, so the attribute has nothing to describe there.
When the <label> wraps a labelable element, the control inside it is the thing that can be valid or invalid. Putting aria-errormessage on the surrounding <label> points assistive technology at an error for an element that cannot have one, which is why the spec rejects it. A "labelable element" is any element a <label> can be associated with: <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>.
Move aria-errormessage to the form control, and pair it with aria-invalid so the message is announced only while the value is invalid.
Examples
Invalid: aria-errormessage on a label that wraps an input
<label aria-errormessage="email-error">
<input type="email" name="email">
</label>
<span id="email-error">Enter a valid email address.</span>
Fixed: move it to the input, with aria-invalid
<label>
<input type="email" name="email" aria-invalid="true" aria-errormessage="email-error">
</label>
<span id="email-error">Enter a valid email address.</span>
The aria-errormessage attribute is not allowed on a <label> element that points to a form control through its for attribute.
aria-errormessage takes the id of the element holding the error text for a form control. Assistive technology announces that text when the control is marked invalid with aria-invalid="true". Both attributes describe the control's value, so both go on the control.
A <label> with a for attribute is associated with the labelable element whose id matches, and its text becomes that element's accessible name. The label has no value and no validity state of its own, so an error reference on it has nothing to describe. The control it names still has no error message attached, and a screen reader user who reaches the invalid field hears no error.
Move aria-errormessage from the <label> to the control named in for, and set aria-invalid="true" on the control while its value fails validation. If the <label> wraps the control instead of using for, the validator reports the same mistake with a different message, saying the <label> is an ancestor of a labelable element.
Invalid example
<input type="checkbox" id="terms" name="terms" required>
<label for="terms" aria-errormessage="terms-error">I accept the terms of service</label>
<span id="terms-error">You must accept the terms to continue.</span>
Valid example
<input type="checkbox" id="terms" name="terms" required aria-invalid="true" aria-errormessage="terms-error">
<label for="terms">I accept the terms of service</label>
<span id="terms-error">You must accept the terms to continue.</span>
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>
A <label> element that wraps a form control, such as an <input> or a <select>, can't have an aria-hidden attribute, whatever its value.
aria-hidden="true" removes an element and everything inside it from the accessibility tree. When a <label> contains a labelable element (button, meter, output, progress, select, textarea, or an input of any type except hidden), hiding the label hides the control too. Keyboard users can still tab to the field, but a screen reader has nothing to announce when they get there, not even the label text that should give the control its name.
The validator reports the attribute on these labels with any value, including aria-hidden="false", so remove it instead of changing it. If the label also points to the control with a for attribute, a second error says the label is associated with a labelable element.
Remove aria-hidden from the <label>. When only part of the label is decorative, such as an icon or the asterisk that marks a required field, put aria-hidden="true" on a <span> around that part. A visually hidden CSS class keeps label text off the screen so that only screen readers read it, and aria-hidden on the same label cancels that. If the whole field has to stay hidden from everyone, as with a spam trap field, put aria-hidden="true" on an element that wraps the label and add tabindex="-1" to the control so keyboard users can't reach it.
Invalid example
<label aria-hidden="true">
Email <span class="required">*</span>
<input type="email" name="email" required>
</label>
Valid example
<label>
Email <span class="required" aria-hidden="true">*</span>
<input type="email" name="email" required>
</label>
An aria-hidden attribute on a <label> element that is associated with a form control hides the label from assistive technologies while the control still expects an accessible name from it. This creates a broken association where the form field appears unlabeled to screen readers.
The aria-hidden="true" attribute removes an element and all its descendants from the accessibility tree. When a <label> is linked to an input (either by wrapping it or through the for attribute), the browser uses that label to compute the input's accessible name. Setting aria-hidden="true" on the label strips away that name, leaving the input without a label for assistive technology users.
The HTML spec explicitly forbids this combination because it guarantees an accessibility failure. A label that exists visually but is hidden from the accessibility tree defeats its own purpose.
To fix this, remove aria-hidden from the <label>. If the label text should not be visible on screen, use a CSS technique to visually hide it while keeping it accessible. If the label itself is truly decorative and another mechanism provides the accessible name (like aria-label on the input), remove the for/id association so the <label> is no longer linked to the control.
Incorrect example
<label for="email" aria-hidden="true">Email</label>
<input type="email" id="email">
Correct examples
Remove aria-hidden from the label:
<label for="email">Email</label>
<input type="email" id="email">
If the goal is to visually hide the label while keeping it accessible, use CSS instead:
<label for="email" class="visually-hidden">Email</label>
<input type="email" id="email">
<style>
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
</style>
If the label is decorative and the input gets its accessible name from another source, break the association:
<label aria-hidden="true">Email</label>
<input type="email" aria-label="Email">
The aria-labelledby attribute is not allowed on a <label> element when that <label> contains a labelable element (such as <input>, <select>, <textarea>, or <button>).
A <label> that wraps a form control gives that control its accessible name from the label's text. aria-labelledby also sets an accessible name, by pointing at other elements through their id. Putting it on the <label> names the label itself, not the control, and browsers differ on whether that referenced text replaces the label's own text when they compute the control's name. A screen reader user may hear the visible label, the referenced text, or a mix of both.
The spec avoids that ambiguity by rejecting aria-labelledby on a <label> that is an ancestor of a labelable element. A "labelable element" is any element a <label> can be associated with: <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>.
If the label's visible text is enough, remove aria-labelledby. If the control needs a name built from other text on the page, put aria-labelledby on the control instead. On the control it overrides the <label>, so include the label's own id in the list to keep the visible text in the name.
Examples
Invalid: aria-labelledby on a label that wraps an input
<h2 id="shipping-heading">Shipping address</h2>
<label aria-labelledby="shipping-heading">
Postcode
<input type="text" name="postcode">
</label>
Fixed: remove the attribute
<h2 id="shipping-heading">Shipping address</h2>
<label>
Postcode
<input type="text" name="postcode">
</label>
Fixed: move it to the input
<h2 id="shipping-heading">Shipping address</h2>
<label id="postcode-label" for="postcode">Postcode</label>
<input type="text" id="postcode" name="postcode" aria-labelledby="shipping-heading postcode-label">
The <a> element is classified as interactive content, meaning it expects user interaction (clicking to navigate). The <label> element is also interactive — clicking a label activates or focuses its associated form control. When a <label> is nested inside an <a>, the browser faces an ambiguous situation: should a click navigate to the link's URL, or should it focus/activate the associated form control? The HTML specification resolves this by simply disallowing the nesting entirely.
According to the WHATWG HTML Living Standard, the content model of the <a> element is "transparent" but must not contain any interactive content. Since <label> is interactive content, it is not permitted as a descendant of <a> at any depth.
Beyond being invalid HTML, this nesting causes real problems:
- Accessibility: Screen readers may announce conflicting roles, confusing users who rely on assistive technology. The purpose of the element becomes unclear — is it a link or a form label?
- Unpredictable behavior: Different browsers may handle the click event differently, leading to inconsistent user experiences.
- Broken form association: The
<label>'sforattribute may not work as intended when the label is trapped inside a link.
The fix is straightforward: if you only need to style text inside a link, use a <span> or another non-interactive element instead of <label>. If you genuinely need both a link and a label, they should be separate, sibling elements rather than nested.
Examples
❌ Invalid: <label> inside <a>
<a href="/settings">
<label>Account Settings</label>
</a>
This triggers the validation error because <label> is interactive content nested inside <a>.
✅ Fixed: Replace <label> with <span>
<a href="/settings">
<span>Account Settings</span>
</a>
If the <label> was only used for styling purposes, a <span> with a CSS class achieves the same visual result without violating the specification.
❌ Invalid: <label> deeply nested inside <a>
<a href="/profile">
<div>
<label for="username">Edit Username</label>
</div>
</a>
The rule applies to all descendants, not just direct children. This is still invalid.
✅ Fixed: Separate the link and label
<label for="username">Edit Username</label>
<a href="/profile">View Profile</a>
When you need both a functional label and a link, keep them as siblings rather than nesting one inside the other.
✅ Fixed: Using <span> with a class for styling
<a href="/dashboard">
<span class="label-style">Dashboard</span>
</a>
.label-style {
font-weight: bold;
text-transform: uppercase;
}
This preserves any visual styling you need while keeping the HTML valid and the interaction model unambiguous.
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 <label> as an element whose content model allows phrasing content but explicitly excludes other <label> elements. When you nest one <label> inside another, browsers cannot determine which form control each label is meant to describe. This breaks the fundamental purpose of the <label> element—providing a clear, one-to-one association between a text description and its corresponding form control.
This issue matters for several reasons:
- Accessibility: Screen readers rely on the
<label>element to announce the purpose of form controls. Nested labels create confusion about which label text belongs to which input, making forms difficult or impossible to navigate for users of assistive technology. - Usability: Clicking a
<label>should focus or activate its associated control. Nested labels create overlapping click targets with unpredictable behavior. - Standards compliance: The WHATWG HTML living standard explicitly states that
<label>elements must not be nested, and validators will flag this as an error.
This error commonly occurs in a few situations: accidentally duplicating closing tags, wrapping a complex form group in a <label> when a <fieldset> would be more appropriate, or using a templating system that inadvertently produces nested labels.
Examples
❌ Nested labels (invalid)
<label>
Full Name
<label>
First Name
<input type="text" name="first-name">
</label>
</label>
❌ Extra closing tag causing a parser issue
A stray closing </label> tag can sometimes cause the browser's error recovery to produce unexpected nesting:
<label>Name</label></label>
<label for="email">Email</label>
While the extra </label> is the root problem here, some parsers and validators may interpret this as a nesting issue. Always ensure your opening and closing tags are properly matched.
✅ Separate labels for separate inputs
<label for="first-name">First Name</label>
<input type="text" id="first-name" name="first-name">
<label for="last-name">Last Name</label>
<input type="text" id="last-name" name="last-name">
✅ Using implicit label association (one label per input)
<label>
First Name
<input type="text" name="first-name">
</label>
<label>
Last Name
<input type="text" name="last-name">
</label>
✅ Grouping related controls with <fieldset> instead of nesting labels
If you need to group multiple labeled inputs under a shared heading, use a <fieldset> with a <legend> instead of wrapping labels inside a label:
<fieldset>
<legend>Full Name</legend>
<label for="first-name">First Name</label>
<input type="text" id="first-name" name="first-name">
<label for="last-name">Last Name</label>
<input type="text" id="last-name" name="last-name">
</fieldset>
This approach provides the grouping semantics you need while keeping each <label> correctly associated with a single form control. The <legend> serves as the group-level description, and each <label> describes its individual input—giving both sighted users and assistive technology users a clear understanding of the form structure.
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 theforattribute to associate a<label>with a specific control'sid. - 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.
The <label> element associates descriptive text with a specific form control, enabling users to click the label to focus or activate the associated input. The for attribute creates this link by referencing the id of the target form control. When the referenced id doesn't correspond to a valid, non-hidden form control, the label becomes orphaned — it isn't associated with anything meaningful.
The W3C validator raises this error in several scenarios:
- The
forattribute references anidthat doesn't exist in the document. - The
forattribute references an element that isn't a labelable element (such as a<div>or<span>). - The
forattribute references an<input type="hidden">, which is not a visible form control and cannot be labeled. - There's a typo or mismatch between the
forvalue and the intended element'sid.
Labelable elements in HTML include <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>.
This matters for accessibility because screen readers rely on the for/id association to announce what each form control represents. Without a valid association, users who depend on assistive technology may not understand what a form field is asking for. It also impacts usability — a properly linked label expands the clickable area for the form control, making it easier to interact with, especially on touch devices and for users with motor impairments.
To fix this issue, verify that the for attribute value exactly matches the id of a visible, labelable form control. If you're labeling a hidden input, consider whether the label is necessary at all (hidden inputs are not user-facing). If the target element isn't a form control, either change it to the appropriate form element or use a different approach like aria-labelledby.
Examples
❌ for references a non-existent ID
<label for="username">Username</label>
<input id="user-name" type="text">
The for value "username" doesn't match the input's id of "user-name".
✅ Fixed: matching for and id
<label for="username">Username</label>
<input id="username" type="text">
❌ for references a hidden input
<label for="token">Token</label>
<input id="token" type="hidden" value="abc123">
An <input type="hidden"> is not a visible form control and cannot be labeled.
✅ Fixed: remove the unnecessary label
<input id="token" type="hidden" value="abc123">
Hidden inputs don't need labels since users never interact with them directly.
❌ for references a non-labelable element
<label for="info">Information</label>
<div id="info">Some details here</div>
A <div> is not a labelable element, so the for association is invalid.
✅ Fixed: use a proper form control or implicit labeling
<label for="info">Information</label>
<textarea id="info"></textarea>
❌ for references an element inside another form control
<label for="opt">Choose one</label>
<select>
<option id="opt" value="a">Option A</option>
</select>
An <option> element is not a labelable element. The label should point to the <select>.
✅ Fixed: reference the <select> element
<label for="choice">Choose one</label>
<select id="choice">
<option value="a">Option A</option>
</select>
Using implicit labeling as an alternative
Instead of using the for attribute, you can wrap the form control inside the <label> element. This creates an implicit association without needing for or id at all:
<label>
Age
<input type="number">
</label>
This approach avoids the for/id mismatch problem entirely and is equally valid for accessibility.
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