Accessible HTML forms

Make errors easy to find and fix.

A red border says something changed. A useful error says what happened, where it happened, and how to recover.

The principle

The form error principle

Do not make people hunt, guess, or rely on color. Bring them to the error and explain the next step.

Do / Don’t

Describe the recovery.

“Invalid” confirms failure but does not help someone succeed. Name the field’s problem in text and include a known correction or example.

Don’tColor plus a vague message

The person must guess what format will be accepted.

<input class="error" value="alex@">
<span>Invalid input</span>
DoIdentify and suggest

Text identifies both the problem and a path to correction.

<input aria-invalid="true"
  aria-describedby="email-error">
<p id="email-error">
  Enter an email address in the format
  name@example.com.
</p>

The error formula

Four parts work together.

No single ARIA attribute creates an accessible error experience. Combine clear language, visible styling, programmatic state, association, and deliberate focus behavior.

Useful text

Identify what is wrong and, when known, how to fix it.

Visible cue

Use text and a strong visual treatment—not color alone.

Programmatic link

Set aria-invalid and connect the message with aria-describedby.

Focus or announcement

After submission, move focus to the field or an error summary. Avoid announcing the same error twice.

Interactive demo

Submit, recover, continue.

Submit an empty or incomplete email address, then observe the complete error state.

  1. The message appears in text.
  2. The input receives aria-invalid="true".
  3. The message is added to the input’s description.
  4. Focus returns to the invalid input.

We will send the receipt to this address.

Implementation

Inspect the pattern.

The error starts hidden. It becomes both visible and programmatically associated only after validation fails. Once corrected, the invalid state and error relationship are removed.

HTML: name, hint, error, and submit action
<label for="email">Email address (required)</label>
<p id="email-hint">We will send the receipt here.</p>
<input id="email" name="email" type="email"
  required aria-describedby="email-hint">
<p id="email-error" hidden></p>
<button type="submit">Send receipt</button>
Invalid state: associate the error and move focus
input.setAttribute('aria-invalid', 'true');
input.setAttribute(
  'aria-describedby',
  'email-hint email-error'
);
error.hidden = false;
input.focus();
Corrected state: remove stale error information
input.removeAttribute('aria-invalid');
input.setAttribute('aria-describedby', 'email-hint');
error.hidden = true;
error.textContent = '';

Quick test

Try every failure path.

  1. Submit the form with each required field empty.
  2. Submit each field with a plausible but invalid value.
  3. Confirm the error is understandable without its color or icon.
  4. Confirm keyboard focus reaches the first error or a useful summary.
  5. With a screen reader, confirm the field’s name, invalid state, and error text are available.
  6. Correct the value and confirm stale error state is removed.

For forms with several errors: add a summary at the top with links to each invalid field. Focus the summary after submission so people understand the scope before correcting individual fields.