AccessibilityApril 29, 202610 min read

Form Labels: The Quiet Conversion Killer

A form is unusable until the user knows what each field is asking. For sighted users that's the visual label; for a screen-reader user it's a label programmatically associated with the field. axe-core's label rule catches every form control where that association never got made.

1. What is the label violation?

The rule checks whether each form control (<input>, <select>, <textarea>) has an accessible name. The name can come from one of four sources:

  1. A <label> with a matching for attribute
  2. A <label> wrapping the input (implicit label)
  3. An aria-label on the input
  4. An aria-labelledby reference to another element

If none of those exist, the rule fires. It's a direct violation of WCAG 2.1 1.3.1 Info and Relationships and 4.1.2 Name, Role, Value.

2. Why it matters

An unlabeled field is a wall to keyboard and screen-reader users. Tabbing into it, the screen reader announces the type and nothing else — “edit, blank” — but no hint about what to type.

It's not just accessibility. The label-input association also expands the touch target on mobile: tapping anywhere on the label focuses the input. That single mechanic makes tiny checkboxes usable with thick fingers. Labels make forms better for everyone.

Forms are where sites earn money. Add to cart, sign up, subscribe — all forms. A poorly labeled form is a direct conversion-rate hit.

3. When does it fire?

  1. No label at all: <input type="text" name="email"> with no nearby <label>.
  2. Mismatched for/id: <label for="mail"> but the input has id="email". Visually adjacent ≠ programmatically linked.
  3. Placeholder used as label: The most popular anti-pattern. The placeholder disappears as soon as the user types.
  4. Hidden label, hidden everywhere: A developer applied display: none. It hides from sight and from assistive tech — net loss.
  5. Multiple labels for one id: Triggers axe-core's sibling rule form-field-multiple-labels and confuses the screen reader.

4. The fixes

4.1 for/id (preferred)

The safest, most widely supported pattern. Clicking the label focuses the input; screen readers read it in the right order.

Yanlış / WrongNo label
<input type="email" name="email" placeholder="Email">
Doğru / Right
<label for="email">Email</label>
<input id="email" type="email" name="email">

4.2 Wrapping (implicit) label

You can wrap the input inside the <label>. Shorter, but support is slightly less consistent across assistive tech than for/id.

Doğru / Right
<label>
  Email
  <input type="email" name="email">
</label>

4.3 Visually hidden label

When design demands the label not be visible (search boxes), hide it visually and keep it in the accessibility tree. Don't use display:none — that erases it everywhere.

Doğru / Rightsr-only label + icon to convey purpose visually
<form role="search">
  <label for="search-q" class="sr-only">Search the site</label>
  <input id="search-q" type="search" name="q" placeholder="Search…">
  <button type="submit" aria-label="Search">
    <svg aria-hidden="true">...</svg>
  </button>
</form>

<style>
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
</style>

4.4 Placeholder is not a label

Yanlış / Wrong
<input type="email" placeholder="Your email">
Doğru / RightLabel persists; placeholder shows a sample value
<label for="email">Email address</label>
<input
  id="email"
  type="email"
  placeholder="[email protected]"
>

4.5 Grouped fields (radio, checkbox)

When several fields answer one question, each field's label isn't enough — the group needs a name too. Use <fieldset> + <legend>.

Doğru / Right
<fieldset>
  <legend>Shipping method</legend>

  <label>
    <input type="radio" name="ship" value="standard">
    Standard (3-5 days) — Free
  </label>

  <label>
    <input type="radio" name="ship" value="express">
    Express (1-2 days) — $9
  </label>
</fieldset>

4.6 React Hook Form / Formik

React form libraries don't generate labels for you. htmlFor (React's for) and id have to match by hand.

Doğru / RightReact Hook Form example
function EmailField() {
  const { register, formState: { errors } } = useFormContext();
  return (
    <div>
      <label htmlFor="email">Email address</label>
      <input
        id="email"
        type="email"
        aria-invalid={errors.email ? "true" : "false"}
        aria-describedby={errors.email ? "email-error" : undefined}
        {...register("email", { required: true })}
      />
      {errors.email && (
        <p id="email-error" role="alert">
          Email is required.
        </p>
      )}
    </div>
  );
}

Bind error messages to the field via aria-describedby. The screen reader then reads label + error in sequence when focus lands.

5. Required fields and error messages

A red asterisk alone is invisible to a screen reader. Use required or aria-required="true".

Doğru / RightVisible + programmatic required indicator
<label for="phone">
  Phone
  <span aria-hidden="true" class="text-red-600">*</span>
</label>
<input
  id="phone"
  type="tel"
  required
  aria-describedby="phone-help"
>
<p id="phone-help" class="text-sm text-slate-600">
  Required
</p>

6. Cases that get missed

  • Multiple labels on one id: Two <label>s pointing at the same input get read twice. Keep one canonical label.
  • Submit button reads "Submit": Generic copy loses form context. Use specifics: “Create my account,” “Subscribe to newsletter,” “Send message.”
  • Custom select components: Hand-rolled dropdowns aren't <select>; they need role="combobox" and aria-labelledby wired up.
  • Date / time pickers: Most custom calendar widgets hide the real input and present visuals. The label must bind to the real input.
  • Captcha fields: reCAPTCHA-style widgets run inside their own iframe and are exempt from the parent page's label rule, but must still be accessible internally.

7. How to test

  1. Tab through the form: Drop the mouse. Listen for “edit” or “blank” with no further info — that's an unlabeled field.
  2. Click the label: If the field doesn't focus, the for/id pair is broken.
  3. axe DevTools / Lighthouse: Both flag missing form labels instantly.
  4. Keysonar SEO Tools: Site-wide crawl that audits every form, covering label, link-name, and button-name in the same report.

8. Quick checklist

  • Every form control has a <label>, aria-label, or aria-labelledby.
  • for/id matches on every label-input pair.
  • Placeholder is used for sample values, not as the label.
  • Visually hidden labels use sr-only, not display:none.
  • Radio/checkbox groups are wrapped in <fieldset> + <legend>.
  • Required fields use required or aria-required (visual * alone is not enough).
  • Error messages are bound to the field via aria-describedby.
  • Submit buttons describe the form's action, not 'Submit'.
  • Custom dropdowns / date pickers carry the right role and aria attributes.

9. References

  • WCAG 2.1 SC 1.3.1 — Info and Relationships — Level A
  • WCAG 2.1 SC 3.3.2 — Labels or Instructions — Level A
  • WCAG 2.1 SC 4.1.2 — Name, Role, Value — Level A
  • HTML Living Standard — The label element
  • WAI-ARIA Authoring Practices: Form patterns

Catch these violations automatically across your site

Keysonar SEO Tools crawls every page, runs the full axe-core ruleset including button-name, and gives you the exact list of affected URLs.

Start free