Form fields with missing or mismatched labels
A delivery form looks fully labelled, but one name is loose text, one label points at the wrong id and one field has a different aria-label, so the words people see are not the fields' names.
The problem
Screen reader and voice control users can't tell which field is which, even though every field looks labelled.
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.
"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.
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.
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.
Try it
Click each label, then press Tab to move through the fields.
Both versions look the same. Does clicking a label put you in its field? Below the form, compare the label you see with the name a screen reader hears.
- Label on screen
- —
- Screen reader hears
- (Tab to a field)
A recreated demo. The broken version is intentionally inaccessible.
The fix
Make the words people see the field's name: a label element, or aria-labelledby to the visible text.
- 1Every field has visible label text that stays on screen while it holds a value.
- 2That visible text is the field's accessible name, through a label element or aria-labelledby.
- 3Don't give a field an aria-label that says something different from its visible label.
<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">Why this fix: Use native HTML first
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".
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.
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.
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
4 checks, no mouse.
Test with: Keyboard, Screen reader, Zoom, 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.
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.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.