Accessible Forms
TL;DR
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 ../01_html/ for the validation half.
Interview Q&A
Q: How do you correctly label a form control?
A: Associate a <label> with the control’s id, or wrap the control:
<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.
Q: Why isn’t a placeholder a label?
A: 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.
Q: How do you make validation errors accessible?
A: Three parts:
- Associate the error text with the field:
aria-describedbypoints at the error element’s id. - Mark invalid:
aria-invalid="true"on the field. - Announce it: render the error in a live region (or
role="alert") so it’s spoken when it appears.
<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).
Q: When do you use <fieldset> and <legend>?
A: To group related controls and give the group a name — essential for radio-button sets and checkbox groups:
<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.
Q: What does the autocomplete attribute do for accessibility?
A: 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.
Q: How do you indicate required and optional fields accessibly?
A: 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 / edge cases
for/idmismatch 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-labelor grouped with a<fieldset>/<legend>). - Disabled vs
aria-disabled—disabledremoves 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 (06_accessible_components.md). - Real-time validation that fires on every keystroke spams live-region announcements — validate on blur/submit, or debounce announcements.
What a senior is expected to say
- “Every control has an associated
<label>— placeholders are hints, not labels. Errors usearia-invalid+aria-describedbyand a live region/role=alert, and never color alone.” - “Radio/checkbox groups go in
<fieldset>/<legend>so the question is announced with each option.” - “Correct
autocompletetokens satisfy 1.3.5 and cut effort for everyone.” - “Native
requiredover custom; on submit I move focus to the error summary or first invalid field.”
Cross-references
- Native validation (Constraint Validation API): ../01_html/
- Live regions for announcing errors: 03_aria.md
- Focus to error summary on submit: 04_keyboard_and_focus.md
- Accessible custom widgets (when native won’t do): 06_accessible_components.md
Further reading
- MDN — Accessible forms / labels: https://developer.mozilla.org/en-US/docs/Learn/Forms/How_to_structure_a_web_form
- WAI Tutorials — Forms: https://www.w3.org/WAI/tutorials/forms/
- MDN —
autocompletevalues: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete