Frontend / Accessibility / 05_forms_accessibility.md

Accessible Forms

Updated 5 interview angles 4 min read source
On this page10
  • How do you correctly label a form control?
  • Why isn’t a placeholder a label?
  • How do you make validation errors accessible?
  • When do you use and ?
  • What does the autocomplete attribute do for accessibility?
  • How do you indicate required and optional fields accessibly?
  • Gotchas
  • Related
  • Further reading
  • Interview angle
  • Accessible Forms

    Forms are where accessibility most often breaks and where it matters most (sign-up, checkout, search). The essentials: every control has a programmatically associated <label> (a placeholder is not a label), errors are announced and tied to their field via aria-describedby + aria-invalid, related controls are grouped with <fieldset>/<legend>, and autocomplete attributes let browsers/AT fill fields. Most of this is native HTML — see the Constraint Validation API in HTML for the validation half.

    How do you correctly label a form control?

    Associate a <label> with the control’s id, or wrap the control:

    html
    <label for="email">Email</label>
    <input id="email" name="email" type="email" autocomplete="email" />
    
    <!-- or wrapping -->
    <label>Email <input type="email" autocomplete="email" /></label>

    A correct label is clickable (bigger hit target), announced by screen readers, and required by WCAG. For an icon-only control with no visible text, use aria-label — but visible labels are better for everyone.

    Why isn’t a placeholder a label?

    placeholder disappears on input (so users forget what the field is), has poor contrast by default, isn’t reliably announced as the name, and can’t be relied on for instructions. Placeholder = optional example/format hint; label = always-visible name. Placeholder-only forms are a classic accessibility (and usability) failure.

    How do you make validation errors accessible?

    Three parts:

    1. Associate the error text with the field: aria-describedby points at the error element’s id.
    2. Mark invalid: aria-invalid="true" on the field.
    3. Announce it: render the error in a live region (or role="alert") so it’s spoken when it appears.
    html
    <label for="pw">Password</label>
    <input id="pw" type="password" aria-invalid="true" aria-describedby="pw-err" />
    <p id="pw-err" role="alert">Must be at least 8 characters.</p>

    For a submit-time summary, render a role="alert" list of errors and move focus to it (or to the first invalid field). Don’t rely on color alone to show “this field is wrong” (WCAG 1.4.1).

    When do you use <fieldset> and <legend>?

    To group related controls and give the group a name — essential for radio-button sets and checkbox groups:

    html
    <fieldset>
      <legend>Shipping speed</legend>
      <label><input type="radio" name="speed" value="std" /> Standard</label>
      <label><input type="radio" name="speed" value="exp" /> Express</label>
    </fieldset>

    Screen readers announce the legend with each option (“Shipping speed, Standard, radio button”), so the question is clear. Without it, the radios are just floating options.

    What does the autocomplete attribute do for accessibility?

    Standard autocomplete tokens (email, name, tel, street-address, cc-number, one-time-code…) let browsers and AT recognize a field’s purpose and autofill it (WCAG 1.3.5 Identify Input Purpose). This reduces effort dramatically for users with motor or cognitive disabilities. Use the correct token, not autocomplete="off" on fields users would want filled.

    How do you indicate required and optional fields accessibly?

    Use the native required attribute (it’s exposed to AT and triggers native validation); aria-required="true" for custom controls that can’t use it. Mark requiredness in the visible label text too (don’t convey it by asterisk-color alone), and state up front whether * means required.

    Gotchas

    • for/id mismatch silently breaks the association — the label looks fine but isn’t connected. Test by clicking the label: focus should move to the input.
    • One label for multiple inputs (split phone/date fields) — give each input its own accessible name (aria-label or grouped with a <fieldset>/<legend>).
    • Disabled vs aria-disabled — disabled removes the control from the tab order and form submission; aria-disabled="true" keeps it focusable (so users can discover it) but you must block its action in JS. Choose deliberately.
    • Error appears but isn’t announced — if the error node is added to the DOM at the same instant, some AT misses it; prefer an always-present live region you populate, or role="alert".
    • Custom dropdowns/date pickers lose all of this unless you rebuild it — prefer native <select>/<input type="date"> or an APG-compliant component (Accessible Components (APG Patterns)).
    • Real-time validation that fires on every keystroke spams live-region announcements — validate on blur/submit, or debounce announcements.

    Further reading

    Interview angle 5

    • “What does every form control need?” - an associated <label>, via for or by wrapping. A placeholder is not a label: it disappears the moment someone types, it fails contrast requirements in most implementations, and voice control users cannot refer to it.
    • “How do you present a validation error?” - aria-invalid on the field, aria-describedby pointing at the message so it is read with the field, and a live region or role="alert" so it is announced when it appears. And never colour alone - a red border communicates nothing to a large number of users.
    • “Why wrap radio groups in a <fieldset>?” - so the <legend> is announced with each option. Without it a screen reader reads “Standard, radio button” with no indication of what question is being answered, which is unusable in a form with several groups.
    • “What does autocomplete buy you?” - it satisfies WCAG 1.3.5 by identifying the field’s purpose programmatically, and it makes the form faster for everyone through autofill. Use the standard tokens; a blanket autocomplete="off" fails the criterion and breaks password managers.
    • “What happens on a failed submit?” - move focus, either to an error summary listing the problems as links, or to the first invalid field. Leaving focus on the submit button means a keyboard user is told nothing happened and a screen reader user has to hunt for what went wrong.