How to Fix Form Error Messages for WCAG Error Identification
When a form goes wrong, sighted mouse users usually see a red border and work out the problem. Screen reader users, people with low vision and people with cognitive difficulties often get nothing useful: no announcement, no text, no idea which field failed or how to fix it. WCAG covers this with three linked requirements: error identification, error suggestion and error prevention. Failing them is one of the most common reasons contact, booking and checkout forms are abandoned. This guide explains what each criterion asks for, shows the HTML and ARIA that ties an error message to its field, how to build an error summary, when to use live regions, and how to customise native HTML validation messages without breaking accessibility.
- What WCAG says about error messages
- Why form errors fail WCAG
- How to test your form errors
- The HTML and ARIA for an accessible error message
- Error summaries, focus and live regions
- Native HTML validation and custom error messages
- The fix, step by step
- Edge cases worth knowing
- Keeping form errors accessible
- Questions and answers
What WCAG says about error messages
Several success criteria apply to forms, and they work together. The core three are about errors directly:
| Criterion | Level | What it requires |
|---|---|---|
| 3.3.1 Error Identification | A | When an input error is detected automatically, the field in error is identified and the error is described to the user in text |
| 3.3.3 Error Suggestion | AA | If you know how to fix the error, tell the user, unless doing so would harm security, for example by revealing which part of a login was wrong |
| 3.3.4 Error Prevention (Legal, Financial, Data) | AA | For orders, payments, legal commitments and changes to stored data, submissions can be reversed, are checked for errors, or can be reviewed and confirmed before they are final |
| 3.3.6 Error Prevention (All) | AAA | The same protection for every form that submits information |
Others support them. 3.3.2 Labels or Instructions (A) requires labels and any format instructions up front. 1.4.1 (A) says colour must not be the only way information is shown, so a red border alone fails. 4.1.3 Status Messages (AA) requires messages such as 3 errors found to be announced without moving focus, if focus does not move. WCAG 2.2 adds 3.3.7 Redundant Entry (A), so a failed submission must not wipe the answers people already gave, and 3.3.8 Accessible Authentication (AA), which matters for login error handling.
In short: an accessible error is written in words, sits next to the field, is connected to the field in code, tells people how to fix it, and is announced or focused so that people who cannot see it still find it.
Why form errors fail WCAG
| Pattern | Who it fails | Criterion |
|---|---|---|
| Red border or red label only, no text | Colour-blind and screen reader users | 1.4.1, 3.3.1 |
| Error text not linked to the field | Screen reader users tabbing back to the field hear nothing | 3.3.1, 1.3.1 |
| Generic message such as Invalid input or Please check the form | Everyone, especially people with cognitive difficulties | 3.3.3 |
| Errors appear silently after submit, focus stays on the button | Screen reader and screen magnifier users | 3.3.1, 4.1.3 |
| Native browser bubbles only, which vanish after a few seconds | Screen magnifier users and people who need more time | 3.3.1 |
| Form clears all fields after an error | Everyone, especially people with motor or memory difficulties | 3.3.7 |
| Live validation announcing on every keystroke | Screen reader users, who are interrupted constantly | Usability, often 4.1.3 |
| Payment taken without a review step | Anyone who mistypes a quantity or address | 3.3.4 |
Most of these come from a theme or form plugin rather than from content, so one fix in the template repairs every form on the site.
How to test your form errors
Automated tools can catch missing labels, but they rarely trigger errors, so this test is manual. It takes ten minutes per form.
- Submit the form empty. Write down what appears, where, and whether focus moved.
- Fill in wrong values: an email without an @, a date in the wrong format, a phone number with letters. Submit again.
- Check that your other answers are still there after the error.
- Turn on NVDA (Windows) or VoiceOver (Mac, Cmd+F5) and repeat. After submitting, can you tell how many errors there are? When you Tab to a failed field, is the error read out with the label?
- In Chrome DevTools, select a failed input and open the Accessibility pane. The computed properties should show Invalid user entry: true and the error text under Description.
You can also list every invalid field and its description from the console:
document.querySelectorAll('[aria-invalid="true"]').forEach(el => {
const ids = (el.getAttribute('aria-describedby') || '').split(' ');
const text = ids.map(id => document.getElementById(id)?.textContent.trim()).join(' | ');
console.log(el.name, '=>', text || 'NO DESCRIPTION');
});
Any field printing NO DESCRIPTION has an error a screen reader user cannot hear.
The HTML and ARIA for an accessible error message
This pattern works in every current browser and screen reader. The label names the field, a hint gives the format before anyone makes a mistake, and the error is linked with aria-describedby:
<div class="field field--error">
<label for="email">Email address</label>
<p id="email-hint" class="hint">We will send your booking confirmation here</p>
<p id="email-error" class="error-text">
<span class="visually-hidden">Error:</span>
Enter an email address in the correct format, like name@example.com
</p>
<input id="email" name="email" type="email" autocomplete="email"
aria-describedby="email-hint email-error"
aria-invalid="true">
</div>
aria-invalid="true"makes screen readers say invalid with the field. Set it only after validation fails, never on page load, and remove it once the value is fixed.aria-describedbyjoins the hint and the error, so both are read after the label when the field is focused. Keep the visible label in thelabelelement; do not move it into anaria-label.- The hidden word Error: gives screen reader users the same signal the red styling gives sighted users.
aria-errormessageexists for this purpose, but screen reader support is still uneven, soaria-describedbyis the safer choice today.
Styling that does not rely on colour
.field--error input { border: 3px solid #b3261e; }
.field--error { border-left: 5px solid #b3261e; padding-left: 12px; }
.error-text { font-weight: 700; }
.error-text::before { content: "\26A0\FE0F "; }
The thicker border, side bar, bold text and warning icon mean the error is still obvious without colour. The red still needs 3:1 contrast against the background for the border and 4.5:1 for the text; see fixing colour contrast.
Writing the message
Say what went wrong and how to fix it, in plain words. Compare Invalid date with Enter your date of birth as day, month and year, for example 27 3 1985. Avoid blame, technical codes and messages that only say a field is required when you could say Enter your postcode.
Error summaries, focus and live regions
On a long form, a list of errors at the top tells people how many problems there are and takes them straight to each field. After a failed submit, show the summary and move focus to it:
<div id="error-summary" class="error-summary" tabindex="-1" hidden>
<h2>There is a problem</h2>
<ul>
<li><a href="#email">Enter an email address in the correct format</a></li>
<li><a href="#phone">Enter a phone number, like 01632 960961</a></li>
</ul>
</div>
form.addEventListener('submit', (e) => {
const errors = validate(form); // your own checks, returns [{id, message}]
if (!errors.length) return;
e.preventDefault();
const list = summary.querySelector('ul');
list.innerHTML = errors.map(er =>
`<li><a href="#${er.id}">${er.message}</a></li>`).join('');
summary.hidden = false;
summary.focus();
document.title = 'Error: ' + document.title.replace(/^Error: /, '');
});
Moving focus to the summary makes screen readers read it at once, so you do not also need role="alert"; using both can cause the summary to be read twice. If the page reloads with errors from the server, render the summary in the HTML and focus it on load. Prefixing the page title with Error: helps people who rely on the title to know a submit failed.
When to use a live region
Use a live region when you show a message without moving focus. A short form validated on submit can place a polite status message such as 2 errors found, please check the highlighted fields in an element with role="status". Do not announce errors as people type; validate when they leave a field or submit, otherwise every keystroke interrupts them.
Native HTML validation and custom error messages
Attributes such as required, type="email", pattern and min give you free validation, and browsers expose the invalid state to assistive technology. The weak point is the message bubble: its wording and timing are set by the browser, it disappears after a few seconds, it cannot be styled and it only shows one error at a time. You can change the wording with setCustomValidity:
const postcode = document.getElementById('postcode');
postcode.addEventListener('input', () => postcode.setCustomValidity(''));
postcode.addEventListener('invalid', () => {
if (postcode.validity.valueMissing) {
postcode.setCustomValidity('Enter your postcode');
} else if (postcode.validity.patternMismatch) {
postcode.setCustomValidity('Enter a postcode in the format AB1 2CD');
}
});
Clear the custom message on every input, or the field stays invalid even after it is corrected. For full control, which most accessible forms need, add novalidate to the form. That stops the bubbles but keeps the validation API, so your script can read input.validity and write persistent messages using the markup above. For styling, the :user-invalid selector, now supported in current browsers, only matches after the user has interacted, which avoids showing errors on a fresh page. More patterns are in fixing form validation.
The fix, step by step
Step 1
Test every form by submitting it empty
Open each contact, booking, account and checkout form and submit it with nothing filled in, then again with deliberately wrong values. Record what appears, whether it is text or only colour, whether focus moves, and whether your other answers survive. Repeat with NVDA or VoiceOver running. This baseline tells you which of error identification, suggestion and prevention each form fails, and which template or plugin you need to change.
Step 2
Add visible labels and format hints
Make sure each input has a visible label tied to it with matching for and id values. Add a hint paragraph before the field wherever there is a format to follow, such as dates, phone numbers or postcodes, and give it an id. Prevention is cheaper than correction. Check in the DevTools Accessibility pane that each field shows the label as its name and the hint under its description.
Step 3
Write errors as text next to each field
For every failed field, show a message in words directly above or below the input, starting with a hidden Error: prefix for screen reader users. Make the message say how to fix the problem, such as Enter a phone number using digits only. Add a thicker border, side bar or icon so the error is visible without colour. Submit wrong values again and confirm each message describes the actual problem.
Step 4
Link errors with aria-describedby and aria-invalid
Give each error element an id and add it to the field's aria-describedby, after the hint id. Set aria-invalid to true when validation fails and remove it when the value becomes valid. Tab to a failed field with a screen reader running and confirm it reads the label, invalid, the hint and the error. In the console, check no invalid field is missing a description.
Step 5
Add an error summary and move focus
Above the form, add a summary container with tabindex set to -1 and a heading such as There is a problem. On a failed submit, list each error as a link to its field, reveal the summary and call focus() on it. Prefix the page title with Error:. Test that the screen reader reads the summary straight away and that each link jumps to the right field.
Step 6
Keep entered data and stop keystroke announcements
Make sure a failed submit, whether checked in the browser or on the server, returns the form with every valid answer still filled in, to meet Redundant Entry in WCAG 2.2. Validate fields when the user leaves them or submits, not on every keystroke, and only announce errors once. If you show a count without moving focus, put it in an element with role set to status that is present on page load.
Step 7
Customise or replace native validation bubbles
If you keep native validation, use setCustomValidity in an invalid handler to replace the browser wording with clear, specific text, and clear it on every input event. For forms that need persistent messages and a summary, add novalidate to the form and read validity in your own script instead. Check in two browsers that messages appear, stay visible and disappear once fixed.
Step 8
Add a review step for orders and payments
For checkout, bookings with payment and anything that changes account data, add a review page showing every answer with change links before the final submit, or let customers cancel within a stated time. This meets Error Prevention for legal, financial and data submissions. Place a test order with a wrong quantity and confirm you can spot and correct it before paying. Then rerun the accessibility audit on the finished form.
Edge cases worth knowing
- Login errors. Error Suggestion has a security exception, so the email or password is incorrect is fine. Do not block pasting into password fields, which would also fail 3.3.8 Accessible Authentication.
- Radio groups and checkboxes. Put the error inside the
fieldset, after thelegend, and link it witharia-describedbyon the fieldset. Link the summary to the first option. - Date fields split into three boxes. Mark only the boxes that are wrong as invalid, and say which part is wrong: Date of birth must include a year.
- Third-party forms. Embedded booking or payment forms are part of your page. Test their errors with a keyboard and screen reader, and raise faults with the supplier.
- Single page apps. In React or Vue, render the error element once and change its text, rather than mounting a new node, so screen readers notice. See accessibility in React.
Keeping form errors accessible
Build the field, hint, error and summary into one reusable component or theme partial, so every new form inherits them. Keep a short list of approved message wordings for common fields. Add the submit-empty test to your checklist for every new form, plugin update and checkout change, and include it in any review against WCAG requirements. For the labels themselves, see fixing form labels, and for broken ARIA attributes found by audits, fixing ARIA errors.
1. What is WCAG error identification?
Error identification is WCAG success criterion 3.3.1, at level A. It says that when your form detects an input error automatically, it must identify which field is wrong and describe the error to the user in text. A red border, an icon with no text or a shake animation is not enough on its own. The text should sit next to the field and be programmatically linked to it, usually with aria-describedby, so screen reader users hear it when they reach the field.
2. What is the difference between error suggestion and error prevention?
Error suggestion, criterion 3.3.3 at level AA, means telling people how to fix an error when you know the answer, such as the expected date format. Error prevention, criterion 3.3.4 at level AA, applies to orders, payments, legal agreements and changes to stored data. It requires that submissions can be reversed, are checked for mistakes, or can be reviewed and confirmed before they are final. Suggestion helps people correct mistakes; prevention stops costly mistakes going through.
3. What is a good WCAG error message example?
A good error message names the problem and the fix in plain words, such as Enter an email address in the correct format, like name@example.com, or Enter a date of birth in the past. It appears as text next to the field, starts with a hidden Error: prefix for screen readers, and uses a thicker border or an icon as well as red. A poor example is a red outline with the word Invalid, or a single message at the top saying there was a problem with no detail.
4. Should I use aria-label for an error message?
No, aria-label is the wrong attribute for error messages. It replaces the field's accessible name, so the label is lost and the error becomes the name, which is confusing. Keep the label in a label element and connect the error with aria-describedby, which adds it as a description read after the name. Set aria-invalid to true on the field while the error is present. The aria-errormessage attribute exists for this job, but support across screen readers is still uneven.
5. Do native HTML validation messages meet WCAG?
Native validation messages can meet the basic requirement on simple forms, because browsers expose the invalid state and the bubble text to assistive technology. They have real weaknesses though: the bubble disappears after a few seconds, shows only one error at a time, cannot be styled for low vision users and uses wording set by the browser. You can improve the wording with setCustomValidity. For important forms, most teams add novalidate and show persistent text errors with an error summary instead.
6. Do I need an error summary at the top of the form?
WCAG does not require an error summary by name, but it is the most reliable way to meet error identification on longer forms. A summary tells people how many errors there are and links to each field, and moving focus to it after a failed submit means screen reader users hear about the problems at once. On a short form with two or three fields, inline messages and moving focus to the first invalid field can be enough. Test both with a screen reader.
7. What changed for error messages in WCAG 2.2?
WCAG 2.2 did not change the error identification, suggestion or prevention criteria, but two new criteria affect how forms handle errors. Redundant Entry, at level A, means people should not have to retype information they already gave in the same process, so a failed submit must keep their answers. Accessible Authentication, at level AA, means login forms must allow pasting and password managers, and must not rely on memory puzzles alone. Both are common failures in checkout and account forms.
The accessibility audit checks your form fields for missing labels, invalid ARIA references and low contrast, so you know which templates to fix before you test the error messages by hand.
Open Accessibility Fixes โ