Accessible form structure

Label every control

Connect visible labels to controls so the form remains understandable, clickable, and navigable with assistive technology.

Every form control needs an accessible name. That’s the text a screen reader announces when the control gets focus, so the person knows what to type.

For a visible field, a real label element is the clearest way to provide it. Connect the two with matching for and id values:

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

Two things happen now. Clicking “Email address” moves focus into the input, which gives you a bigger click target for free. And a screen reader announces “Email address, edit text” when the input gets focus.

Wrapping instead

You can also wrap the control inside the label:

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

No id needed, because the connection comes from nesting. Both patterns work.

I prefer explicit for and id when the label and the control sit in different layout elements. Wrapping is handy for checkboxes and radio buttons, where the label sits right next to the control anyway.

A placeholder is not a label

This one comes up in every code review:

<input name="email" type="email" placeholder="Email address">

The text disappears the moment you start typing. Then you’re looking at ada@ and can’t remember what the field was for. Placeholder text also tends to have weak contrast, and many screen readers treat it differently from a label.

Use a placeholder for an example of the format, if you need one. Keep the label as a label.

name is a different job

Don’t confuse the label with the name attribute. name creates the key the server receives. A control with a name and no label submits fine, and is still unusable for someone who can’t see the layout. You need both.

Icon-only controls

A search button that’s only a magnifying glass has no text to announce. Prefer visible text when you have room. When you don’t, give the button a short accessible name:

<button type="submit" aria-label="Search">
  <svg aria-hidden="true">...</svg>
</button>

The screen reader says “Search, button”. The icon is hidden from it, because the name already says everything.

Two tests I run on every form. First, click every label and confirm the matching control gets focus. Second, open DevTools, select each control, and look at the Accessibility pane. It shows the computed name. If it says empty, someone is going to get stuck on that field.

Lesson completed