/ โ† Accessibility Fixes / Accessible Error Messages
In this guide
  1. What WCAG says about error messages
  2. Why form errors fail WCAG
  3. How to test your form errors
  4. The HTML and ARIA for an accessible error message
  5. Error summaries, focus and live regions
  6. Native HTML validation and custom error messages
  7. The fix, step by step
  8. Edge cases worth knowing
  9. Keeping form errors accessible
  10. 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:

CriterionLevelWhat it requires
3.3.1 Error IdentificationAWhen 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 SuggestionAAIf 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)AAFor 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)AAAThe 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

PatternWho it failsCriterion
Red border or red label only, no textColour-blind and screen reader users1.4.1, 3.3.1
Error text not linked to the fieldScreen reader users tabbing back to the field hear nothing3.3.1, 1.3.1
Generic message such as Invalid input or Please check the formEveryone, especially people with cognitive difficulties3.3.3
Errors appear silently after submit, focus stays on the buttonScreen reader and screen magnifier users3.3.1, 4.1.3
Native browser bubbles only, which vanish after a few secondsScreen magnifier users and people who need more time3.3.1
Form clears all fields after an errorEveryone, especially people with motor or memory difficulties3.3.7
Live validation announcing on every keystrokeScreen reader users, who are interrupted constantlyUsability, often 4.1.3
Payment taken without a review stepAnyone who mistypes a quantity or address3.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.

  1. Submit the form empty. Write down what appears, where, and whether focus moved.
  2. Fill in wrong values: an email without an @, a date in the wrong format, a phone number with letters. Submit again.
  3. Check that your other answers are still there after the error.
  4. 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?
  5. 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>

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.

โš ๏ธ A live region must already exist in the page before you put text into it. Creating the element and filling it at the same moment is often not announced.

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

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.

Questions and answers

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.

Find unlabelled fields and broken ARIA on your 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 โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All Accessibility Fixes  ·  Fixing Form Validation  ·  Fixing Form Labels  ·  Fixing ARIA Errors  ·  WCAG Requirements Explained  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.