Accessibility · Forms and Errors

Label Association and the Accessible Name of a Form Control

How the for attribute and implicit nesting bind a label to a control, how that binding becomes an accessible name, and what an unassociated label leaves behind.

A form field on a screen is two objects that happen to sit near each other. One is a box that accepts input and has a role: text field, combobox, checkbox. The other is a run of text that says what the box is for. Nothing in the rendering connects them. The connection exists only if the markup asserts it, and the assertion is separate from the layout: a label placed directly above its field, in the same table cell, styled to look like part of the control, contributes nothing to what a screen reader announces unless the document declares the relationship.

The declaration comes in exactly two forms, and everything else about form labelling follows from how those two forms behave at the edges.

The two association mechanisms

The explicit form is an attribute pair: a <label> element carrying a for attribute whose value equals the id of a control.

<label for="email">Email address</label>
<input id="email" name="email" type="email">

The implicit form nests the control inside the label, with no for attribute at all:

<label>Email address
  <input name="email" type="email">
</label>

Both produce the same association in the document’s internal model, and the browser exposes the same accessible name either way. The mechanisms differ in what they constrain. Implicit nesting requires the text and the control to be in the same subtree, which rules out layouts where the label must precede the field in one container and the field lives in another. Explicit association uses a document-wide identifier, so the label can be anywhere: before the field, after it, in a preceding table column, in a different section entirely.

The for attribute only binds to elements that the specification calls labelable. There are seven element types in that set: button, input (excluding type="hidden"), meter, output, progress, select, and textarea. A for value that names a div or a heading binds nothing and produces no error; the markup parses, the page renders, and the control stays unnamed. This silent failure is the reason the association is worth checking mechanically rather than by inspection.

A control can be the target of more than one explicit label. Two <label for="email"> elements that point at the same id both apply, and the accessible name becomes their text joined in document order. Duplicate labels are legitimate in tables where a column header and a row header both describe the same cell-like input, but they are also a common accident when a field is duplicated during a redesign and the old label is left behind. The result is a name that reads as two sentences stapled together.

From association to accessible name

The name a screen reader speaks for a text input is computed at the moment it is needed, not stored in the page. The computation walks a fixed precedence chain and stops at the first source that yields a non-empty string. For an <input type="email"> the chain is: aria-labelledby, then aria-label, then the associated <label> element or elements, then the title attribute, then the placeholder attribute. Native association sits third, which is why an aria-label silently overrides a visible label that a designer spent time on, and why a placeholder only ever serves as a last resort, a fallback whose failures are set out in Placeholders as a Fallback Name.

Because the label text is taken from the label element’s subtree, it is not necessarily the literal text between the tags. If the label contains an image, the image’s alt text joins the name; if it contains a nested span carrying an abbreviation, that text joins too. The name is the concatenation of the accessible text of the label’s descendants, whitespace-normalised to single spaces. A label written across three source lines with indentation gets a name with single spaces, not the newlines and tabs of the source.

The reverse direction matters as much. Descendants of a label that are themselves exposed to the accessibility tree, such as a link, a button, or a second focusable control, are announced as separate objects with their own names, and their text also joins the label’s name. A label containing a “What is this?” help link therefore names its control with a string that includes the words “What is this”, which reads as part of the field’s name rather than as an aside. There is no attribute that suppresses a descendant’s contribution while keeping it interactive.

What an unassociated label leaves behind

When the text and the control are not bound, the control’s computed name falls through to title, then placeholder, then nothing. A text input with no name announces its role and its current value and nothing else: “edit text, blank”. The user knows there is a field, knows it is empty, and does not know what to type. Tab order still reaches it, so the failure is not that the field is skipped; it is that arriving at the field conveys no information.

Adjacency is the trap because it is visually indistinguishable from association for a sighted user. Three patterns produce it repeatedly.

A label in one table cell and the input in the next cell, with the for attribute omitted because the visual alignment seemed sufficient. A label rendered as a <div> styled bold above the field, never as a <label>. A label that carries for but points at an id that exists twice in the document, or that was renamed when the field was, or that points at a wrapper around the input rather than the input itself. The wrapper case is the most instructive: a for pointing at a <div> that contains the input binds nothing, because a div is not labelable, and the input inside stays unnamed.

Association also changes pointer behaviour, which is a second, independent reason to declare it. Clicking a label transfers focus and activation to its control; the label’s box becomes part of the control’s hit area. For a checkbox, this converts a target that may be 13 by 13 CSS pixels into the full width of the label text, which is the difference between a control that can be hit with a thumb and one that cannot. The WCAG 2.2 minimum target size criterion sets 24 by 24 CSS pixels as the floor for pointer targets; a well-associated label clears that floor without any extra CSS and without enlarging the visible checkbox.

Grouping, order, and the limits of a single name

A label names one control. When several controls share a question, such as a set of radio buttons for one choice or a group of checkboxes for several, each control’s label names only its own option, so the shared question has no home in the per-control name. The accessible name of the radio button labelled “Yes” is “Yes”, and “Yes” is not a question. The mechanism for the shared text is a <fieldset> with a <legend>, covered in Fieldset and Legend Group Semantics; the point here is that adding the question text to every option’s label is not a substitute. It produces a group in which each option is announced with the whole question repeated, which is both verbose and misleading about which part is the option.

Source order constrains what the user hears next. The name is announced with the control, and the description, the string produced by aria-describedby, is announced after it. Everything a form says about a field therefore has a fixed sequence: role, name, value, state, description. Instructions written before the field in source order but attached with aria-describedby are heard after the value, not before, which is often the wrong place for a format hint. Instructions that must be heard first belong in the label or in the group name, at the cost of lengthening the name for every visit to the field.

Two further limits are worth stating because they surface in audits.

A <label> is not focusable and cannot be reached by keyboard on its own. Its text is available only through the control it names, which means a label bound to nothing is invisible to a keyboard or screen reader user even though it is plainly visible on screen. There is no workaround that preserves the visual design and restores the text; the binding has to be added.

The association also does not survive shadow boundaries. A for attribute resolves within the same tree, so a label in the light DOM cannot reference an id inside a custom element’s shadow root, and a control projected into a shadow tree loses the association it had outside. Components that wrap an input must forward the labelling information into the shadow tree through their own mechanism, typically by rendering the label inside the same root or by accepting a label string and applying it as an accessible name. Getting this wrong is the characteristic labelling failure of component libraries, and it looks exactly like the adjacency mistake: the label is on screen, the field is on screen, and the name is missing.

The constraint that ties the whole subject together is that a name is computed, not authored. Every article about validation and error recovery downstream of this one assumes the field can be named at all, because an error message attached to an unnamed field is a message with no subject. The association is the cheapest thing in the form and the one whose absence is hardest to notice from the outside.

References

  1. HTML Living Standard: Forms — WHATWG
  2. Accessible Name and Description Computation 1.2 — W3C
  3. HTML Accessibility API Mappings 1.0 — W3C