A red border and a shake animation might look like an error, but to a screen reader user they are silence. If the only signal that something went wrong is color or motion, the form is broken for the people who most need clear feedback. Accessible form error messages are the difference between a user fixing a typo in five seconds and abandoning the checkout entirely.
This guide is for developers who already wire up validation and now need to make it perceivable, programmatically associated, and announced. We cover WCAG 3.3.1 Error Identification (Level A), 3.3.3 Error Suggestion (AA), and 4.1.3 Status Messages (AA), then turn them into concrete patterns using aria-describedby, aria-invalid, and deliberate focus management.
What WCAG actually requires for form errors
Three success criteria do most of the work, and developers routinely conflate them. Keeping them straight tells you exactly what each error message must do.
- 3.3.1 Error Identification (Level A): when an input error is automatically detected, the item in error must be identified and the error described to the user in text. Text is the operative word, because a red outline alone fails when it conveys the error through color only.
- 3.3.3 Error Suggestion (Level AA): when you know how to fix the error, say so. 'Invalid date' identifies the error; 'Enter the date as DD/MM/YYYY' suggests the fix. The latter is what 3.3.3 requires whenever a correction is known and revealing it won't compromise security or the purpose of the content.
- 4.1.3 Status Messages (Level AA): a message reporting an error, success, or state change without moving focus must still be announced by assistive technology. This is the criterion that governs inline validation and error summaries that appear after a failed submit.
Two related criteria round out robust forms: 3.3.2 Labels or Instructions (A), so users know the expected format before they type, and 3.3.4 Error Prevention (AA) for legal, financial, and data submissions, which requires that such submissions be reversible, checked, or confirmable. For the underlying definitions, see our WCAG reference.
Associating the message with the field: aria-describedby and aria-invalid
A visible error message floating near a field is not enough. Screen readers move through forms field by field, and unless the message is programmatically tied to the input, it is never read when the user focuses that input. aria-describedby is the link.
The pattern: give the error element an id, reference that id from the input's aria-describedby, and set aria-invalid='true' on the input. When the field becomes valid, remove the error element (or drop the reference) and set aria-invalid back to 'false'.
A correctly wired email field looks like this:
- <label for="email">Email address</label>
- <input id="email" type="email" aria-invalid="true" aria-describedby="email-error">
- <p id="email-error">Enter an email address in the format name@example.com</p>
Now a screen reader announces something like 'Email address, edit, invalid data, Enter an email address in the format name@example.com' when focus lands on the field. Three details developers get wrong:
- Do not set aria-invalid='true' on page load or before the user has interacted. Marking every required field invalid up front is noise and misrepresents state. Add it only after validation fails.
- aria-describedby can hold multiple space-separated ids, so a field can reference both a hint ('Your password') and an error ('Password must be at least 12 characters'). Both are announced, in DOM order.
- Keep the message in the DOM, not a CSS ::after pseudo-element or a title attribute. Pseudo-element text is not reliably exposed to assistive tech, and title tooltips are unavailable to keyboard and touch users.
Announcing errors that appear after submit: 4.1.3 and live regions
aria-describedby works when the user navigates to a field. But when a user presses Submit and validation fails, errors often appear without focus moving anywhere, so the user has no idea anything changed. That is exactly what 4.1.3 Status Messages addresses.
You have two viable strategies, and they are not interchangeable.
Strategy one is to move focus to an error summary. After a failed submit, render a summary box at the top of the form listing each error as a link to its field, then call .focus() on the summary's heading or container (give it tabindex='-1'). Because focus moves, the summary content is read immediately, and the links let users jump straight to broken fields. This is the GOV.UK error summary pattern, and it is excellent for forms with several errors.
Strategy two is a live region for lightweight or single-field feedback. When you don't want to move focus (for example, inline validation as a user leaves a field), put the message in a live region so it is announced in place:
- <div role="alert"> is implicitly assertive and interrupts the user; reserve it for genuine errors.
- <div aria-live="polite"> waits for a pause; use it for success confirmations and non-urgent updates.
- The container must exist in the DOM before you inject text into it. If you create the element and its message in the same render, many screen readers miss the change. Render the empty live region first, then populate it.
Avoid stacking redundant announcements: if you move focus to a summary, don't also fire an assertive role='alert', or the message is read twice. Pick one mechanism per event.
Focus management and writing the message itself
After the error summary is read, the user should be able to activate a link and land directly in the offending field. Wire each summary link's href to the field id (href='#email') and ensure activating it moves focus to the input, not just scrolls to it; for non-native targets, manage focus in your click handler. This satisfies keyboard users.
The wording matters as much as the plumbing. A programmatically perfect 'Error' tells no one anything. Good error text is specific, in plain language, and actionable:
- Identify the field and the problem: 'First name is required' rather than 'This field is required' floating with no anchor.
- Suggest the fix when you know it (3.3.3): 'Enter an amount between 1 and 10,000' rather than 'Invalid amount'.
- Don't rely on color alone (1.4.1 Use of Color): pair the red text with an icon or the word 'Error', and ensure the text meets 4.5:1 contrast. Red error text on white frequently fails, so verify it with a contrast checker.
- Keep the message visible while the user types the correction, and clear it the moment the input becomes valid so stale errors don't linger.
One more practical rule: don't disable the Submit button to communicate validation state. A disabled button gives no explanation, is often skipped by screen readers, and traps users who can't tell why they're stuck. Let them submit, then report errors accessibly.
A quick verification checklist
Before shipping a form, run these checks, ideally with an actual screen reader, since automated tools catch missing associations but not whether your wording makes sense:
- Tab to each invalid field: is the error text announced via aria-describedby, and is the field reported as invalid?
- Submit with errors: is the user informed without having to hunt, via a focused summary or a live region?
- Is every error conveyed in text, not color or icon alone, and does it suggest a correction where one is known?
- Does activating a summary link move focus into the right field?
- Run an automated pass to catch the obvious misses: our free accessibility scan flags unlabeled inputs and missing associations in seconds.
Accessible forms are also a compliance baseline. Under the European Accessibility Act, the EN 301 549 standard maps to WCAG 2.2 Level A and AA, and it applies to in-scope digital products and services from 28 June 2025. Error Identification (3.3.1) is a Level A requirement you cannot skip. See our European Accessibility Act overview for how this fits together.