← Back to the Build Your Homepage series
EPISODE 03
label · fieldset · errors · keyboard · focus management

Accessible Forms

Forms are the most accessibility-critical part of your app. Pair labels properly, group with fieldset, announce errors clearly, and manage focus.

formlabelfieldseterrorsfocus
Duration
About 1.5 hours
Level
📊 Intermediate
Prerequisite
🎯 a11y-02
OUTCOME
Forms that any keyboard or screen-reader user can complete

What you'll learn

  • 1Pair every input with a real <label>
  • 2Group related inputs with <fieldset> + <legend>
  • 3Show validation errors with aria-describedby and aria-invalid
  • 4Move focus to the first error on submit

1. Real Labels (not Placeholders)

html
<!-- Bad: placeholder is not a label -->
<input placeholder="Email">

<!-- Good: explicit label -->
<label for="email">Email</label>
<input id="email" name="email" type="email">

<!-- Or wrapped -->
<label>
  Email
  <input name="email" type="email">
</label>

2. fieldset for Groups

html
<fieldset>
  <legend>Shipping address</legend>
  <label>Street <input name="street"></label>
  <label>City <input name="city"></label>
</fieldset>

<fieldset>
  <legend>Newsletter</legend>
  <label><input type="radio" name="news" value="weekly"> Weekly</label>
  <label><input type="radio" name="news" value="monthly"> Monthly</label>
</fieldset>

3. Error Messages

html
<label for="pw">Password</label>
<input id="pw" type="password" aria-describedby="pw-help pw-err" aria-invalid="true">
<span id="pw-help">At least 8 characters.</span>
<span id="pw-err" role="alert">Password too short.</span>
💡

Show errors near the input, link them with aria-describedby, and announce them with role="alert".

4. Focus on Submit Error

javascript
function onSubmit(e) {
  e.preventDefault();
  const errors = validate(form);
  if (errors.length) {
    const first = form.querySelector(`[name="${errors[0].name}"]`);
    first?.focus();
    return;
  }
  // ... submit ...
}
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗