Image description: Road sign with a left-pointing arrow symbolizing direction or choice.
Accessible error messages
Accessible error messages: The complete guide
Every form field you create, every validation you trigger, every failure state you display — these are moments where digital products either include or exclude millions of people. Here’s how to get every one of them right.
The hidden cost of unhelpful errors
You have probably seen it: a form that tells you “Invalid input” after you’ve carefully typed your phone number. A checkout process that marks a field red but offers no explanation. An error message that uses cryptic codes understood only by the developer who wrote it at 11pm on a Friday.
These aren’t minor UX inconveniences. For the 1.3 billion people worldwide who live with some form of disability, poorly crafted error messages can make a digital product completely unusable. For screen reader users, an error message that only changes color is invisible. For users with cognitive disabilities, a vague one-liner like “Something went wrong” is paralysing. For someone relying on voice control, a form error that steals focus unpredictably breaks the entire workflow.
Accessible error messages are not about adding ARIA attributes as an afterthought. They require intentional language, appropriate DOM structure, visual and non-visual signalling, and a deep respect for the diversity of your users. This guide walks through every dimension of getting them right — from WCAG criteria to copywriting principles to live code patterns.
We focus on client-side and server-side validation errors in web interfaces, though most principles apply equally to native mobile apps, kiosk terminals, and voice interfaces. References are to WCAG 2.2 AA unless otherwise noted.
What the standard actually demands
WCAG 2.2, published in October 2023, is the current benchmark for web accessibility and the reference point for most legal frameworks — including the EU Web Accessibility Directive, the UK Equality Act, and the US ADA. Several success criteria apply directly to error messages. Understanding the letter of each one is essential before you can go beyond them.
| Criterion | Level | What it requires | For error messages |
|---|---|---|---|
| 1.3.1 Info and relationships | A | Convey structure and relationships through semantics, not only through presentation. | An error state shown only via colour fails. The relationship between a field and its error must be programmatically determinable — use <label>, aria-invalid="true", and aria-describedby to associate the error text with the input. |
| 1.4.1 Use of colour | A | Don’t use colour as the only visual means of conveying information. | Red borders alone are insufficient. Add an icon, text label, or pattern. |
| 1.4.3 Contrast (minimum) | AA | Text must have at least 4.5:1 contrast ratio (3:1 for large text). | Error text in light red (#f87171) on white achieves only ~2.93:1 — far below the required 4.5:1. Use deeper tones: #b91c1c on white delivers ~7.1:1 and passes comfortably. |
| 2.4.3 Focus order | A | Focus order must preserve meaning and operability. | Mismanaged focus after a failed submission can break the logical sequence (e.g. focus lands after the error, making it unreachable). Moving focus to an error summary on submission failure is a recommended technique — primarily serving SC 3.3.1 and 3.3.2 — that also keeps the focus order coherent under 2.4.3. |
| 3.3.1 Error identification | A | If an input error is detected, the item is identified and the error is described in text. | The error must be described in text — not just visually indicated. |
| 3.3.2 Labels or instructions | A | Provide labels or instructions when content requires user input. | Format hints (e.g. “DD/MM/YYYY”) must be present before submission, not only on error. |
| 3.3.3 Error suggestion | AA | If an error is detected and suggestions are known, provide them. | “Invalid date” is insufficient. “Enter a date in DD/MM/YYYY format, e.g. 23/03/2026” is correct. |
| 3.3.4 Error Prevention | AA | For legal/financial/data submissions, provide review, confirm, or reversal. | Allow users to review and correct before final submission. |
| 4.1.3 Status messages | AA | Status messages can be programmatically determined through role or property without receiving focus. | Use role="alert", role="status", or aria-live so AT users receive the message. |
Many teams interpret “identified and described in text” as simply adding a label. But the field with the error must be identifiable — meaning the error message must either be adjacent to the field or the field’s name must appear in the error text. “Please correct the highlighted fields” does not comply.
What makes a good error message
A well-formed error message has four non-negotiable components. Each one serves a distinct purpose — remove any one of them, and you degrade the experience for some portion of your users.
The language framework: Six rules for error copy
Error messages are a form of UX writing. They are not log output. They are not developer notes. They are human communication under stress — written for someone who just failed at something and needs to succeed.
1. Be specific
Tell the user exactly what is wrong. “Invalid” tells them nothing. “Password must be at least 8 characters” tells them everything.
2. Be actionable
The message should imply or directly state how to fix the error. Include format examples where the format is non-obvious.
3. Avoid blame
Never say “you entered”, “your input”, or “you failed”. Prefer passive constructions: “The email address entered is incomplete.”
4. Use plain language
Write at a reading age of around 11. Avoid jargon, technical codes, and passive-aggressive phrasing like “please try again.”
5. Time it correctly
Show inline errors on blur (when the user leaves the field), not on every keystroke. Show summary errors only after submission attempt.
6. Consider the context
A payment form error deserves more reassurance than a search filter. Match tone and detail to the stakes of the task.
Real Error Messages: Before & After
The gap between an inaccessible and an accessible error message is often just a few words and two attributes. These comparisons show exactly what changes — and why each change matters.
Required field validation
Problems: Doesn’t identify which field. Provides no action. Often surfaced only with colour change (fails 1.4.1). No role="alert" means screen readers won’t announce it.
Why it works: Names the field. Explains why it’s needed. Linked to the input via aria-describedby. Text contrast ≥ 4.5:1. Screen readers announce it on submit via role="alert".
Format validation (date)
Problems: User doesn’t know what format is expected. Tells them what’s wrong, but not how to fix it. Fails 3.3.3 (Error Suggestion). No example provided.
Why it works: Specifies the required format. Gives a concrete example (satisfies 3.3.3). Concise and plain. Would be announced by screen readers immediately on field blur.
Password validation
Problems: “Weak” is subjective and provides no fix path. Typically shown with a red strength bar — which is colour-only feedback (fails 1.4.1). No relationship between bar state and AT output.
Why it works: Lists each requirement. Users can scan and address each gap. The strength indicator supplements this text and uses both colour AND text label (“Weak / Strong”). No information carried by colour alone.
Server / system error
Problems: Technical code is meaningless to most users. “Something went wrong” provides no action path. Injected into DOM without live region — silent for screen readers. No recovery suggestion.
Why it works: Human language. Confirms what failed. Gives two clear recovery paths. Injected into an aria-live="assertive" region so screen readers announce it immediately. Focus moved to the message.
The best error message is one that prevents the error from happening in the first place. The second-best error message is one that makes recovery trivially obvious.
Error patterns & when to use them
Different error scenarios call for different interaction patterns. Choosing the wrong container or announcement mechanism is one of the most common accessibility failures in forms.
| Pattern | Best for | ARIA implementation | Focus behaviour | Required |
|---|---|---|---|---|
| Inline field error | Immediate field-level validation (on blur) | aria-invalid="true" + aria-describedby |
No focus movement. Error heard on next focus of field. | Required |
| Error summary banner | Multi-field form submission errors | role="alert" or aria-live="assertive" |
Move focus to the summary heading on submission. | Required for forms >3 fields |
| Toast / snackbar notification | Non-form errors (save failed, network issue) | role="alert" or aria-live="assertive" |
Do NOT move focus. Auto-dismiss after ≥5 seconds (or never). | Recommended |
| Modal dialog error | Destructive action confirmation, critical errors | role="dialog" + aria-modal="true" + aria-labelledby |
Move focus to first interactive element inside dialog. | For critical/irreversible errors |
| Page-level alert region | Server errors returned after page reload | role="alert" at top of <main> |
Should be first focusable element after skip link. | Required if present on load |
| Status polite message | Low-urgency feedback (auto-saved, optional field skipped) | aria-live="polite" + role="status" |
No focus movement. Announced after current speech finishes. | Optional / context-dependent |
The dual-layer approach for multi-field forms
For forms with three or more fields, a single pattern is insufficient. The Government Digital Service (GDS) and the ARIA Authoring Practices Guide both recommend a dual-layer system: an error summary at the top of the form, plus inline error messages beside each affected field.
On submission with errors: (1) Inject an error summary container at the top of the form with a heading like “There are 3 errors. Fix these to continue.” and links to each erroneous field. (2) Move keyboard focus to that summary heading. (3) Each link in the summary jumps to the corresponding field. (4) Each field displays its own inline error message linked via aria-describedby. Screen reader users hear both layers; sighted users see both layers. Nobody is left behind.
Code patterns that work
Understanding the theory is half the battle. Below are production-ready patterns for the most common scenarios, annotated with the WCAG criteria each attribute satisfies.
Inline field validation
<!-- The label is always present and always linked --> <div class="field-group"> <label for="email"> Email address <span class="required-indicator" aria-label="required"> *</span> </label> <input type="email" id="email" name="email" autocomplete="email" aria-required="true" aria-invalid="true" <!-- set when invalid --> aria-describedby="email-hint email-error" /> <!-- Hint text always shown (WCAG 3.3.2) --> <p id="email-hint" class="field-hint"> We'll send your confirmation here. </p> <!-- Error message (shown only on error) --> <p id="email-error" class="field-error" role="alert" aria-live="assertive" > <!-- inject error text here --> Enter a complete email address — for example, [email protected] </p> </div>
Error summary (multi-field forms)
<!-- Inject this above the form on submission, then focus #error-summary --> <div id="error-summary" role="alert" aria-labelledby="error-summary-title" tabindex="-1" <!-- allows programmatic focus --> class="error-summary" > <h2 id="error-summary-title"> There are 2 errors. Fix these to continue. </h2> <ul> <li><a href="#email">Email address: enter a complete email</a></li> <li><a href="#dob">Date of birth: use DD/MM/YYYY format</a></li> </ul> </div> <!-- JavaScript: move focus to summary after injection --> <script> const summary = document.getElementById('error-summary'); summary.focus(); // fires after DOM update </script>
Dynamic / AJAX errors (no page reload)
When errors are returned asynchronously — from an API call, for example — you can’t rely on page load to surface them. The ARIA live region pattern is essential here.
// Set up the live region once on page load (empty, hidden) // <div id="live-error" role="alert" aria-live="assertive" aria-atomic="true"></div> async function submitForm(data) { try { const res = await fetch('/api/save', { method: 'POST', body: data }); if (!res.ok) throw new Error(await res.text()); announceStatus('Your changes have been saved.', 'polite'); } catch (err) { announceStatus( 'We couldn\'t save your changes. Try again or contact support.', 'assertive' ); } } function announceStatus(message, urgency = 'polite') { const region = document.getElementById('live-error'); region.setAttribute('aria-live', urgency); // Clear first to re-trigger announcement if same message region.textContent = ''; requestAnimationFrame(() => { region.textContent = message; }); }
Screen readers only announce content that changes in a live region. If you inject the same error message twice in a row, some ATs won’t re-announce it. The workaround: clear the region to an empty string, then populate it in a requestAnimationFrame callback. This forces the DOM mutation the AT is listening for.
The error accessibility audit checklist
Use this checklist during design review, code review, and QA. It covers all Level A and Level AA WCAG 2.2 criteria relevant to error states.
Visual & content
- Error is communicated through text, not colour alone (WCAG 1.4.1)
- Error text has ≥ 4.5:1 contrast ratio against its background (WCAG 1.4.3)
- An icon or visual marker supplements the colour change
- Error message names the field or is physically adjacent to it
- Error message explains what is wrong (not just “invalid”)
- Error message includes how to fix the error (WCAG 3.3.3)
- Format examples are provided for date, phone, postcode, and similar fields
- Language is plain, non-technical, and non-blame-assigning
- Error text does not disappear until the error is resolved
- Format hints (e.g. “DD/MM/YYYY”) are shown before submission, not only on error
Programmatic & AT
aria-invalid="true"is set on the erroneous input- Error message element is linked via
aria-describedbyon the input - Error container uses
role="alert"oraria-live="assertive" - Error region is not empty in the DOM before errors exist (pre-populated empty container)
- Error summary is injected and focused programmatically on form submission failure
- Error summary links navigate to the corresponding form field
- Screen reader testing passes on NVDA/Firefox and VoiceOver/Safari
- Page title updates to reflect error state on full-page form submissions
- Status messages use
role="status"witharia-live="polite"for non-critical feedback
Focus & keyboard
- Focus moves to the error summary (or first error field) after failed submission
- Focus indicator is clearly visible on all error-state interactive elements (WCAG 2.4.11)
- Modal error dialogs trap focus within the dialog
- Dismissing an error (e.g. closing a toast) returns focus to a logical position
- No error interaction requires a pointing device
- Timeout errors give adequate warning and time to extend session (WCAG 2.2.1)
WCAG 3.3.6 (Error Prevention — All) asks you to let users check, confirm, and reverse all submissions — not just financial ones. While this is AAA and not legally required in most contexts, it significantly reduces error-induced stress for users with cognitive disabilities. Consider it the gold standard worth pursuing.
How we got here: A brief timeline
role="alert" and aria-live patterns. Consistent browser and AT support finally achievable for dynamic error announcements.
Error messages as a mirror of your product’s values
The error message is one of the most revealing moments in any digital experience. It arrives precisely when a user is under stress — when they’ve made a mistake, or when something beyond their control has gone wrong. What you do in that moment says everything about how seriously you take inclusion.
A red border with no text is not an error message. A code dump is not an error message. A vague “something went wrong” is not an error message. An accessible error message is a piece of clear, kind, actionable human communication that works for everyone — including the 26% of users who experience some form of disability, the 15% who rely on keyboard navigation, and the 100% of users who will encounter an error at some point.
Accessibility is not a feature. It is a quality attribute of every feature you ship — and error messages are where that quality is tested under the worst conditions.
The good news is that accessible error messages require very little extra effort when incorporated from the beginning. The ARIA attributes are minimal. The copy guidelines are clear. The patterns are well-established. What they require is intention — a deliberate decision to test with assistive technologies, to write for humans instead of systems, and to treat every failure state as an opportunity to earn trust.
At AIOPSGROUP, a valantic company, we build digital products that work for everyone. Accessibility engineering is embedded in our design system, our code reviews, and our QA process — not bolted on at the end. If your team needs help auditing, implementing, or building an accessible design system from the ground up, we’d be glad to help.
WCAG 2.2 Quick Reference — Input Assistance · ARIA Authoring Practices Guide · GDS Error Message Component · WebAIM Million Report 2024