Klar Barrierefrei / wcag

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.