WCAG 1.3.1 · rule: label
How to fix form inputs without labels
The most common form mistake is treating grey placeholder text as a label. Here is why that fails and how to associate a real label with every control.
Last updated: · By Paweł Dziura
The myth: "the placeholder is the label"
The single most widespread accessibility mistake in web forms is treating grey placeholder text as if it
were a label. It looks clean — one less line of visible text, a tidy input with "Email address" floating
inside it — and it's wrong for at least four concrete reasons. The placeholder vanishes the
moment someone types, so they lose the field's name exactly when they might need to check it.
Its low-contrast grey usually fails contrast requirements. It is not reliably
exposed to every screen reader as the field's accessible name. And it gives users with memory
or attention differences nothing to fall back on. A placeholder is a hint, never a label. This
is common enough that axe ships a dedicated rule for it,
placeholder-is-not-a-label-attribute, alongside the core label rule.
What a label actually does
WCAG 2.1 criterion 1.3.1 (Info and Relationships, Level A) requires that the visual
relationship between a field and its name is also available programmatically, and
4.1.2 (Name, Role, Value, Level A) requires every form control to expose an accessible
name. A properly associated <label> satisfies both at once. It also does a favour for
every sighted mouse user: clicking the label focuses or toggles the control, enlarging the hit target —
which matters enormously for small checkboxes and radio buttons.
The bad pattern, and three good ones
Here is the placeholder-as-label anti-pattern, plus an input whose label isn't associated at all:
<input type="email" placeholder="Email address">
<!-- label present but not linked to the input -->
<label>Email address</label>
<input type="email" id="email">
The fix is to tie a real label to the control. Any of these is correct; the explicit for/
id pairing is the most robust:
<!-- 1. explicit association (preferred) -->
<label for="email">Email address</label>
<input type="email" id="email">
<!-- 2. wrapping the input -->
<label>Email address
<input type="email">
</label>
<!-- 3. no visible label by design (e.g. a search box) -->
<input type="search" aria-label="Search products">
The awkward cases
Real forms have controls that don't map cleanly. A group of radio buttons needs each
input individually labelled and the whole set wrapped in a
<fieldset> with a <legend> naming the question. A field split
across several inputs (a phone number in three boxes) needs each box named so it isn't just "edit text".
And if you genuinely want a label to be invisible for visual design, keep it in the DOM with a
sr-only class rather than deleting it — hiding it with display:none removes it
from the accessibility tree too.
How our scanner detects it
The label rule fires on every form control — input (except hidden/button
types), select, and textarea — that resolves to no accessible name via an
associated <label>, aria-label, or aria-labelledby. When we
find a control whose only descriptive text is a placeholder, the
placeholder-is-not-a-label-attribute rule flags it specifically. Both come back with the
element's selector. What automation can't confirm is whether your label wording is clear, or
whether a fieldset's legend actually describes the right question — those are
judgement calls we hand off for manual review.