A form is unusable until the user knows what each field is asking. For sighted users that's the visual label; for a screen-reader user it's a label programmatically associated with the field. axe-core's label rule catches every form control where that association never got made.
1. What is the label violation?
The rule checks whether each form control (<input>, <select>, <textarea>) has an accessible name. The name can come from one of four sources:
- A
<label>with a matchingforattribute - A
<label>wrapping the input (implicit label) - An
aria-labelon the input - An
aria-labelledbyreference to another element
If none of those exist, the rule fires. It's a direct violation of WCAG 2.1 1.3.1 Info and Relationships and 4.1.2 Name, Role, Value.
2. Why it matters
An unlabeled field is a wall to keyboard and screen-reader users. Tabbing into it, the screen reader announces the type and nothing else — “edit, blank” — but no hint about what to type.
It's not just accessibility. The label-input association also expands the touch target on mobile: tapping anywhere on the label focuses the input. That single mechanic makes tiny checkboxes usable with thick fingers. Labels make forms better for everyone.
Forms are where sites earn money. Add to cart, sign up, subscribe — all forms. A poorly labeled form is a direct conversion-rate hit.
3. When does it fire?
- No label at all:
<input type="text" name="email">with no nearby<label>. - Mismatched for/id:
<label for="mail">but the input hasid="email". Visually adjacent ≠ programmatically linked. - Placeholder used as label: The most popular anti-pattern. The placeholder disappears as soon as the user types.
- Hidden label, hidden everywhere: A developer applied
display: none. It hides from sight and from assistive tech — net loss. - Multiple labels for one id: Triggers axe-core's sibling rule
form-field-multiple-labelsand confuses the screen reader.
4. The fixes
4.1 for/id (preferred)
The safest, most widely supported pattern. Clicking the label focuses the input; screen readers read it in the right order.
<input type="email" name="email" placeholder="Email"><label for="email">Email</label>
<input id="email" type="email" name="email">4.2 Wrapping (implicit) label
You can wrap the input inside the <label>. Shorter, but support is slightly less consistent across assistive tech than for/id.
<label>
Email
<input type="email" name="email">
</label>4.3 Visually hidden label
When design demands the label not be visible (search boxes), hide it visually and keep it in the accessibility tree. Don't use display:none — that erases it everywhere.
<form role="search">
<label for="search-q" class="sr-only">Search the site</label>
<input id="search-q" type="search" name="q" placeholder="Search…">
<button type="submit" aria-label="Search">
<svg aria-hidden="true">...</svg>
</button>
</form>
<style>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>4.4 Placeholder is not a label
<input type="email" placeholder="Your email"><label for="email">Email address</label>
<input
id="email"
type="email"
placeholder="[email protected]"
>4.5 Grouped fields (radio, checkbox)
When several fields answer one question, each field's label isn't enough — the group needs a name too. Use <fieldset> + <legend>.
<fieldset>
<legend>Shipping method</legend>
<label>
<input type="radio" name="ship" value="standard">
Standard (3-5 days) — Free
</label>
<label>
<input type="radio" name="ship" value="express">
Express (1-2 days) — $9
</label>
</fieldset>4.6 React Hook Form / Formik
React form libraries don't generate labels for you. htmlFor (React's for) and id have to match by hand.
function EmailField() {
const { register, formState: { errors } } = useFormContext();
return (
<div>
<label htmlFor="email">Email address</label>
<input
id="email"
type="email"
aria-invalid={errors.email ? "true" : "false"}
aria-describedby={errors.email ? "email-error" : undefined}
{...register("email", { required: true })}
/>
{errors.email && (
<p id="email-error" role="alert">
Email is required.
</p>
)}
</div>
);
}Bind error messages to the field via aria-describedby. The screen reader then reads label + error in sequence when focus lands.
5. Required fields and error messages
A red asterisk alone is invisible to a screen reader. Use required or aria-required="true".
<label for="phone">
Phone
<span aria-hidden="true" class="text-red-600">*</span>
</label>
<input
id="phone"
type="tel"
required
aria-describedby="phone-help"
>
<p id="phone-help" class="text-sm text-slate-600">
Required
</p>6. Cases that get missed
- Multiple labels on one id: Two
<label>s pointing at the same input get read twice. Keep one canonical label. - Submit button reads "Submit": Generic copy loses form context. Use specifics: “Create my account,” “Subscribe to newsletter,” “Send message.”
- Custom select components: Hand-rolled dropdowns aren't
<select>; they needrole="combobox"andaria-labelledbywired up. - Date / time pickers: Most custom calendar widgets hide the real input and present visuals. The label must bind to the real input.
- Captcha fields: reCAPTCHA-style widgets run inside their own iframe and are exempt from the parent page's label rule, but must still be accessible internally.
7. How to test
- Tab through the form: Drop the mouse. Listen for “edit” or “blank” with no further info — that's an unlabeled field.
- Click the label: If the field doesn't focus, the for/id pair is broken.
- axe DevTools / Lighthouse: Both flag missing form labels instantly.
- Keysonar SEO Tools: Site-wide crawl that audits every form, covering label, link-name, and button-name in the same report.
8. Quick checklist
- Every form control has a <label>, aria-label, or aria-labelledby.
- for/id matches on every label-input pair.
- Placeholder is used for sample values, not as the label.
- Visually hidden labels use sr-only, not display:none.
- Radio/checkbox groups are wrapped in <fieldset> + <legend>.
- Required fields use required or aria-required (visual * alone is not enough).
- Error messages are bound to the field via aria-describedby.
- Submit buttons describe the form's action, not 'Submit'.
- Custom dropdowns / date pickers carry the right role and aria attributes.
9. References
- WCAG 2.1 SC 1.3.1 — Info and Relationships — Level A
- WCAG 2.1 SC 3.3.2 — Labels or Instructions — Level A
- WCAG 2.1 SC 4.1.2 — Name, Role, Value — Level A
- HTML Living Standard — The label element
- WAI-ARIA Authoring Practices: Form patterns