# Form fields with missing or mismatched labels

Source: https://easeweb.dev/learn/form-labels
Topics: Forms > Every field has a label that matches what is shown (WCAG 1.3.1, 2.5.3, 3.3.2)
The fix: Give every field a visible label that stays on screen and is also its accessible name. Either turn the existing text into a label element tied to the field with for and id, or keep the text where it is and point the field at it with aria-labelledby. Remove any aria-label that says something different from what is shown.
Test with: keyboard, screen reader, zoom, forced colors
References: https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html https://www.w3.org/WAI/tutorials/forms/labels/ https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A flower shop's checkout asks where to deliver. Three fields, "Full name", "Postcode" and "Phone", each have their name in bold above the box. It looks like any well-built form, and a sighted mouse user fills it in without trouble. A screen reader user who presses Tab hears "edit text" on the first field, "edit text" again on the second and "Contact number, edit text" on the third. A voice control user says "click Phone" and nothing happens. Nothing on screen hints at any of this.

## Why it fails

"Full name" and "Phone" are `div` elements. They sit next to the fields, but nothing in the code connects them, so the first field has no accessible name. That fails 1.3.1: the relationship people see is not in the markup. "Postcode" is a real `label`, but its `for="post-code"` doesn't match the field's `id="postcode"`, so it labels nothing: that also fails 1.3.1, and the field has no name. The phone field has `aria-label="Contact number"`, which replaces the visible "Phone" as its name. 2.5.3 needs the visible words to be part of the name, so speaking what you see works.

## Who is affected

Screen reader users hear a field type with no purpose, or a different name from the one a sighted colleague would read out to them. Voice control users say the label they can see, and the field doesn't respond. Everyone loses the larger click target a real label gives: clicking "Full name" or "Postcode" does nothing.

## What automation and AI miss

A visual review finds nothing: every field has its name above it. axe's `label` rule finds the first two fields, but it counts the `aria-label` as a name, so the phone field passes even though it doesn't match what is shown. A quick AI fix often adds `aria-label` to every field: the scanner goes quiet, the broken `for` stays broken, and the names may still differ from the printed text. Whether the name matches the visible words is something a person has to compare.

## Before: text that looks like labels, but isn't

```html
<div class="field-title">Full name</div>
<input name="name" type="text">

<label for="post-code" class="field-title">Postcode</label>
<input id="postcode" name="postcode" type="text">

<div class="field-title">Phone</div>
<input name="phone" type="tel" aria-label="Contact number">
```

It looks the same as the fixed version. But the first field has no name, the second's label points at an id that doesn't exist, and the third is named "Contact number" while the screen says "Phone".

## After: label elements

```html
<label for="name">Full name</label>
<input id="name" name="name" type="text" autocomplete="name">

<label for="postcode">Postcode</label>
<input id="postcode" name="postcode" type="text" autocomplete="postal-code">

<label for="phone">Phone</label>
<input id="phone" name="phone" type="tel" autocomplete="tel">
```

Each `label` is tied to its field by a matching `for` and `id`, so the printed words are the name: a screen reader says "Full name, edit text", voice control responds to "click Phone", and clicking the label moves focus to the field. The postcode's `for` now matches its `id`, and the `aria-label` is gone, so nothing overrides the visible text.

## After: point aria-labelledby at the existing text

```html
<div id="name-label" class="field-title">Full name</div>
<input name="name" type="text" autocomplete="name" aria-labelledby="name-label">

<div id="postcode-label" class="field-title">Postcode</div>
<input name="postcode" type="text" autocomplete="postal-code" aria-labelledby="postcode-label">

<div id="phone-label" class="field-title">Phone</div>
<input name="phone" type="tel" autocomplete="tel" aria-labelledby="phone-label">
```

When a template or component library won't let you change the `div` to a `label`, give the text an `id` and point the field at it. The field's name is then exactly the visible words, so screen readers and voice control get the same label as sighted users. The postcode's `label` with its mistyped `for` becomes plain text like the others, and the phone field loses its `aria-label`.

## Which option to use

Both make the visible text the field's name and pass 1.3.1, 2.5.3 and 3.3.2. Use label elements: they do the same job with plain HTML, and clicking the label focuses the field, which aria-labelledby does not do. Keep aria-labelledby for markup you can't restructure, such as a third-party template, and replace it with a label when you can.

## Implementation decisions

**Placeholders are hints, not labels.** Replacing the visible name with a placeholder is the other common way to lose a label: it vanishes as soon as the field has a value, which fails 3.3.2. A placeholder can show an example, such as "SW1A 1AA", but only next to a real label. Grey placeholder text is also often below the contrast a label needs.

**Wrapping is fine too.** `<label>Phone <input type="tel"></label>` associates the label without `for` and `id`. It is the same fix as the first option, and it suits components that can't generate ids. Some older voice control software is more reliable with `for` and `id`.

**Label first in the name.** If a field needs more context for screen reader users, add it with `aria-describedby` or hidden text after the visible label, so the name starts with the words people see. "Phone, for the courier" still passes 2.5.3; "Courier contact, phone" is fragile.

**Hidden labels.** A search box with a visible "Search" button can take its label from that button with `aria-labelledby`. A visually hidden label is a last resort: it names the field for screen readers but leaves 3.3.2 depending on the surrounding design.

**Components.** Make the label a required prop of the shared field component and generate the `id` and `for` together in the component. A hand-typed `for` that is one character off, as in the example, is invisible in review.

## Verify the fix

1. Inspect each field in the browser's accessibility tree and check its name is the visible label text, starting with the same words, with no aria-label replacing it.
2. With a screen reader, Tab through the form and check each field is announced with the printed label, then click each label and check focus moves to its field.
3. With voice control, say "click" followed by each visible label and check that field takes focus.
4. Fill in every field, then check every label is still visible, including at 200% zoom and in forced colors.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared field component changes.

## Limits

This is a starting pattern, not a guarantee for every application. Grouped controls such as radio buttons need a `fieldset` and `legend` as well, which this guide doesn't cover. Fields added by third-party widgets, payment iframes or content editors need the same check. Test the complete journey with your target assistive technology.
