Skip to main content
Saved

Accessible Forms

Category Accessibility

Level Beginner

Examples

Native web

Create forms with proper labels, error messages, and state announcements for all users.

In this article
  1. Problem
  2. Solution
  3. Example
  4. Dynamic Error Announcement
  5. Form-Level Success Messaging
  6. Multiple Description References
  7. Benefits
  8. Tradeoffs
  9. Summary
  10. See also

Accessible Forms

Problem

Screen reader users land on an unlabeled input with no idea what it’s asking for. The placeholder says “Email,” but placeholders often don’t get announced.

I’ve tested forms where validation errors appeared as red text next to the input, and screen readers never announced them. Users would submit, get rejected, and have no idea why. They’d try again with the same data, get the same rejection, and eventually give up.

Keyboard users run into their own problems: a tab order that skips inputs, invisible focus indicators, and submit buttons that can’t be reached without a mouse.

Solution

Every input needs a label: a <label> element with a for attribute matching the input’s id. Clicking the label moves focus to the input, and screen readers announce the label when users land on the field.

The required attribute is what tells screen readers a field is required. A visual asterisk next to the label adds nothing for them, so it gets aria-hidden="true", and a short line at the top of the form explains what the asterisk means for sighted users. When the form shows its own error messages, novalidate on the <form> turns off the browser’s built-in validation bubbles while keeping required in place.

When validation fails, red text on its own isn’t enough, because the error also needs to be announced. role="alert" on an error message makes screen readers speak it immediately, aria-invalid="true" marks the invalid field, and aria-describedby connects the field to its error message.

Related inputs belong in a <fieldset> with a <legend>. Radio buttons for “Payment method” make no sense individually, and the legend gives screen readers that missing context.

DOM order should match visual order. If CSS moves things around visually, tab order still follows the DOM, and keyboard users end up jumping around the page.

Example

An accessible form with proper labels, announced errors, and grouped inputs:

<form novalidate>
  <p>Fields marked * are required.</p>

  <label for="email">Email address <span aria-hidden="true">*</span></label>
  <input
    id="email"
    type="email"
    aria-describedby="email-error email-hint"
    aria-invalid="true"
    required
  />
  <div id="email-hint">We'll never share your email</div>
  <div id="email-error" role="alert">Please enter a valid email address</div>

  <label for="username">
    Username <span aria-hidden="true">*</span>
  </label>
  <input id="username" type="text" required />

  <fieldset>
    <legend>Newsletter frequency</legend>
    <label><input type="radio" name="frequency" value="daily" /> Daily</label>
    <label><input type="radio" name="frequency" value="weekly" checked /> Weekly</label>
    <label><input type="radio" name="frequency" value="monthly" /> Monthly</label>
  </fieldset>

  <label>
    <input type="checkbox" aria-describedby="terms-error" />
    I agree to the terms and conditions
  </label>
  <div id="terms-error" role="alert"></div>

  <button type="submit">Create account</button>
</form>

Dynamic Error Announcement

Announce validation errors dynamically using ARIA:

function validateEmail(input) {
  const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.value);
  const errorEl = document.getElementById(`${input.id}-error`);

  input.setAttribute('aria-invalid', !isValid);
  errorEl.textContent = isValid ? '' : 'Please enter a valid email address';
}

document.getElementById('email').addEventListener('blur', function() {
  validateEmail(this);
});

Form-Level Success Messaging

A live region announces submission success to screen reader users:

<div role="status" aria-live="polite" aria-atomic="true" id="form-status"></div>

<script>
function handleFormSubmit(event) {
  event.preventDefault();
  document.getElementById('form-status').textContent =
    'Account created successfully. Redirecting...';
  setTimeout(() => { window.location.href = '/dashboard'; }, 2000);
}

document.querySelector('form').addEventListener('submit', handleFormSubmit);
</script>

Multiple Description References

A single input can reference multiple descriptions for hints, requirements, and errors:

<label for="password">Password</label>
<input
  id="password"
  type="password"
  aria-describedby="password-hint password-requirements password-error"
/>
<div id="password-hint">Choose a strong password</div>
<div id="password-requirements">At least 15 characters. Spaces are allowed.</div>
<div id="password-error" role="alert"></div>

Benefits

  • Screen reader users know what to enter. Labels tell them exactly what each field expects, and error messages get announced rather than just displayed.
  • Form completion rates go up when more people can use your form.
  • WCAG compliance becomes achievable, since proper labels and error handling are WCAG requirements.
  • Clear labels and immediate feedback help every user, and keyboard navigation works properly.

Tradeoffs

  • HTML gets more verbose. Labels, IDs, and aria-describedby references add markup to maintain.
  • Wrong ARIA is worse than no ARIA. Point aria-describedby at a non-existent ID and screen readers have nothing to announce, so test your work.
  • Automated checkers only catch obvious issues; you need to test with real screen readers since NVDA, JAWS, and VoiceOver all behave differently.
  • CSS frameworks may conflict with your structure, expecting HTML patterns that don’t match accessible form markup.
  • Too many live region announcements overwhelm users. If every keystroke triggers an announcement, you’ve created a new accessibility problem.
  • Some JavaScript frameworks break label-input associations by generating dynamic IDs. Using both aria-required and HTML required can cause duplicate announcements.

Summary

Accessible forms require proper label associations, meaningful error messages, and logical tab order. ARIA handles errors that appear during validation, and keyboard support makes the form usable without a mouse. The extra markup gets you WCAG compliance and a form that’s easier for everyone to use.

See also

Newsletter

A Monthly Email
from Den Odell

Behind-the-scenes thinking on frontend patterns, site updates, and more

No spam. Unsubscribe anytime.