Form inputs have labels
Why it matters
An <input> without <label htmlFor>, aria-label, or a wrapping <label> parent is announced by screen readers as "edit text" with no indication of what the field is for — on a checkout or signup form this turns every submission into a guessing game and drives abandonment. The Target v. NFB $6M settlement cited form-label failures as a lead claim, and unlabeled form fields sit as the #2 most common claim in the ~3,600 annual federal ADA website lawsuits tracked by Seyfarth Shaw (#1 is missing alt text). The most common anti-pattern is using the placeholder attribute as the sole label, which visually looks labeled to a sighted developer but disappears on focus, provides no accessible name, and forces users with cognitive or memory impairments to delete their input just to re-read the prompt. AI coding tools produce this failure constantly because modern Tailwind-shaped UI snippets often omit <label> in favor of placeholder text for visual compactness, and because form libraries that wire up labels via id wiring frequently lose the wiring when a model refactors a form. Browser autofill and password managers also rely on labels to decide what to autofill, so unlabeled fields quietly break password saving.
Severity rationale
High because form-label failures drove the Target v. NFB $6M settlement and rank as the #2 claim in ~3,600 annual federal ADA website lawsuits — the exposure is an active plaintiff's-bar target, not theoretical.
Remediation
Pair each input with a <label>:
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" />
Deeper remediation guidance and cross-reference coverage for this check lives in the accessibility-fundamentals Pro audit — run that after applying this fix for a more exhaustive pass on the same topic.
Detection
- ID:
form-inputs-have-labels - Severity:
high - What to look for: Enumerate
<input>,<textarea>,<select>, and custom form controls (<Combobox>,<Switch>,<Checkbox>,<RadioGroup>, shadcn controls) in JSX/TSX. Excludetype="hidden|submit|button|reset|image"from the denominator. For each remaining input, check for one of: (a)<label htmlFor={id}>with matchingid; (b) a wrapping<label>containing both input and text; (c)aria-label="..."; (d)aria-labelledby="..."pointing at visible text; (e) shadcn<FormField>+<FormLabel>; (f) shadcn<Label htmlFor={id}>paired with the input. - Pass criteria: ≥98% of detected inputs (after exclusions) have an association via one of the six mechanisms.
- Fail criteria: >2% unlabeled. Placeholder-as-label auto-fails —
<input placeholder="Email" />disappears on focus and doesn't announce to screen readers. A<span>/<div>of label-looking text adjacent withouthtmlFor/aria-labelledbywiring does NOT count. shadcn<Label>withouthtmlFormatching the input'siddoes NOT count. - Skip (N/A) when: No form inputs. Quote:
"0 <input>/<textarea>/<select> elements (after exclusions) in JSX/TSX". - Report even on pass:
"N inputs (after exclusions); M have labels via {mechanism}. Coverage M/N = X%". - Detail on fail:
"3 of 32 inputs unlabeled (9% — should be ≤2%). Placeholder-only at components/search-bar.tsx:12; app/contact/page.tsx:24 (<input name='phone' />)". - Remediation:
<label htmlFor="email">Email</label> <input id="email" name="email" type="email" />
Taxons
History
- 2026-04-22·v1.0.0·Initial import from project-snapshot via Phase 8.1 bundling·by phase-8-1-bundle-project-snapshot
- 2026-04-22·v2.0.0·Phase 9 consequence-first restructure — moved to new section slug, added news/incident references, severity reviewed.·by phase-9-stack-scan-v3
- 2026-04-23·v2.1.0·Phase 9.1 tighten — coverage threshold ratcheted 90% → 98%; placeholder-only-as-label explicitly auto-fails; shadcn FormField/Label wrappers recognized.·by phase-9-1-stack-scan-v3-1
- 2026-04-25·v2.1.1·v3.1.0 pre-ship trim — prose compression for under-80K MCP cap; merged overlapping Fail-criteria / Do-NOT-pass-when sections; compressed enumeration prose; one remediation example per pattern. No semantic change; anti-sycophancy guards preserved.·by phase-9-1-stack-scan-v3-1