reala11y GUIDES Accessible formvalidation errors:aria-invalid and… Jun 10, 2024

June 10, 2024 · reala11y team

Accessible form validation errors: aria-invalid and focus

Make WordPress form errors usable: associate messages with fields via aria-describedby, set aria-invalid, manage focus, and announce inline validation. WCAG 3.3.1 and 3.3.3.

A label tells a user what to type. An error message tells them why what they typed was rejected — and that second job is where most WordPress forms quietly fall apart. The field turns red, focus stays put, and a screen-reader user, who never saw the red, submits the same broken form again. They have no idea anything went wrong.

Two success criteria govern this. 3.3.1 Error Identification (Level A) requires that when input is rejected, the error is described to the user in text. 3.3.3 Error Suggestion (Level AA) goes further: when you know how to fix it, suggest the fix. This guide is the mechanics of doing both accessibly. It assumes you’ve already got visible, associated labels — if not, start with our accessible forms guide.

Identify the error in text, not just colour

The baseline failure is signalling an error by appearance alone:

<!-- Inaccessible: the only signal is a red border -->
<input type="email" class="has-error">

A red border communicates nothing to a screen reader and nothing to a colour-blind user who can’t distinguish it. WCAG 3.3.1 asks for a text message that names the problem. WCAG 3.3.3 asks you to make it actionable where you can:

The suggestion is what turns a dead end into a path forward.

Wire the message to the field

A message floating near a field isn’t enough — assistive technology needs the association in the markup. Use aria-describedby to point the input at its error text, and set aria-invalid="true" so the field announces its failed state:

<label for="email">Email address</label>
<input
  type="email"
  id="email"
  name="email"
  aria-invalid="true"
  aria-describedby="email-error"
>
<p id="email-error">Email address is not valid. Use a format like [email protected].</p>

Now a screen reader announces the label, the “invalid” state, and the error text together. Two things to keep honest:

Announce errors that appear dynamically

Inline (client-side) validation introduces a new problem: the error appears after the page has loaded, so a screen reader won’t read it unless you tell it to. Put error text inside a live region so it’s announced when it changes.

For a single field validated on blur, role="alert" on the message container works well — it’s an assertive live region, so it interrupts and reads immediately:

<p id="email-error" role="alert">
  Email address is not valid. Use a format like [email protected].
</p>

For a summary that appears on submit, a polite live region is usually calmer:

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

A reliable rule of thumb: use role="alert" for an urgent, single-error correction, and aria-live="polite" for a status summary the user isn’t actively waiting on. Don’t wrap the entire form in a live region — you’ll flood the user with announcements on every keystroke.

Manage focus on submit

When a form fails validation, leaving focus wherever the submit button was strands keyboard and screen-reader users. Two patterns work:

// On a failed submit, move focus to the summary so it's read out.
const summary = document.getElementById('error-summary');
summary.setAttribute('tabindex', '-1');
summary.focus();

The summary-with-links pattern (popularised by the GOV.UK Design System) is the gold standard for multi-field forms, because it gives an overview and a route to each individual error.

The WordPress reality

Most WordPress forms come from plugins — Contact Form 7, Gravity Forms, WPForms, Forminator, the comment form, WooCommerce checkout. Their accessibility varies widely, and you usually can’t rewrite their markup directly. What you can do:

A scanner can catch the structural part of this — a field flagged aria-invalid with no matching describedby, an empty error container, a control with no accessible name. reala11y surfaces these code-level issues in your rendered HTML. What it can’t judge is whether your message text is useful: “invalid input” passes a structural check and still fails the user. That’s the honest boundary — automated tooling, ours included, typically detects roughly 30–40% of WCAG issues by criteria, and message quality lives in the human-review remainder.

The honest takeaway

Accessible validation is four moves: identify the error in text, suggest the fix where you can, wire the message to its field with aria-describedby and aria-invalid, and move focus somewhere useful. Get those right and you’ve covered the mechanics of 3.3.1 and 3.3.3. Then do the thing no tool can do for you: fill your own form out wrong, with the mouse untouched and a screen reader on, and confirm every error is spoken and every fix is clear. For the full criteria, see the WCAG 2.2 guide.