How to make inline error messages accessible

category: tutorials

On one of my projects I am helping a governmental organisation to take their application forms to the web. They are mostly very complex forms (for reasons). We do our best to help people fill out the forms correctly and identify incorrect input to them where we can. In this post I will go into some ways to do this accessibly.

Commonly, when an error occurs, an error message is inserted into the page. If you want to do this accessibly, some things to look at are identifying errors as errors, notifying the (screenreader) user of new content and describing what happened in clear language. Below, I will go into how you can improve things in those three areas.

As per WCAG 3.3.1 Error Identification, this is what we need to do:

If an input error is automatically detected, the item that is in error is identified and the error is described to the user in text.

Yes, we can script it

Before we go into identifying validity and error messages, a little note about using JavaScript.

In the old days, a form submit would just trigger the same page to reload, now with error messages where applicable. In 2017, this is still a good practice, but it can be hugely enhanced by preventing the submit and displaying errors client side. And why wait until the submit? You could insert an error message as soon as a user tabs out of a field.

Shouldn’t accessible forms avoid JavaScript? No, on the contrary. They can use JavaScript to inform users about errors as they occur. Combined with a server side approach, this can give us the best of both worlds.

Needless to say: using good old HTML elements is key to your forms to be successful. In this post I go into some ARIA techniques, but always keep in mind that the first rule of ARIA is to not use ARIA. Paraphrasing, this rule means that if you can do it with HTML, don’t use ARIA. The exception to this for things that are not available in HTML. Whether error messages getting injected into the page fall under that umbrella is a case of ‘it depends’. There is the native Constraint Validation API, but it is problematic in many ways (see PPK’s 3 part article on form validation), so in this article I am assuming we are rolling our own validation with JavaScript.

Indicating a field has invalid input

When you detect a field is invalid, you can apply the aria-invalid attribute to it. Then, make sure your scripts are watching the relevant field, so that you can remove the attribute once it no longer applies.

If you were using native HTML form validation, you could style invalid input using the :invalid pseudo class. If your JavaScript is determining what’s valid our not and the aria-invalid attribute is added/removed appropriately, it provides a convenient way of indicating invalid input:

[aria-invalid] { border-color: red; border-right: 5px; }


Adding and removing the aria-invalid attribute also helps users of screen readers recognise they are on an invalid field. Fields with the attribute are read out as being invalid in JAWS, NVDA and VoiceOver.

Conveying that an error message appeared

When your script has detected input of a field is not valid, it inserts an error message into the page. Make sure it is easy to see that this is an error message, for example by using a combination of colors, an icon and/or borders. It also helps if you indicate that it is an error by prefixing the actual message with ‘Error: ’.

To make sure our error messages are read out to users with screen readers, we need to ensure the accessibility tree is notified of the newly inserted content.

There are two ways that are roughly equivalent, both making use of ARIA attributes: live regions (aria-live) and role=alert (avoid using both at the same time). They turn an HTML element into a live region, which means that a screenreader will read out changes to that element. A change could be that something is appended to the element. Note that for this to work, the element itself has to be present in the DOM on page load. Live regions or role=alert work in VoiceOver and most versions of JAWS and NVDA.

In the implementation I describe below, this would be the flow:

Live region

To turn an HTML element into a live region, add aria-live to it. There are various modes, depending on whether you want updates to be read out immediately or not. In our case, we do want that, and will use aria-live="assertive":

<div aria-live="assertive">
<!-- insert error messages here -->

If we are watching for fields to become incorrect, it makes sense to apply the same functionality in reverse. If the field is changed and input is now valid, we can remove the errors. We need our live region to be aware of both addition and removal of error messages. With aria-relevant we can set it up to do exactly this:

<div aria-live="assertive" aria-relevant="additions removals">
<!-- insert error messages here -->

(Codepen with live region examples)

If we want to control whether we need the whole region to be read out, we can use aria-atomic. If it is set to true, the whole live region will be read out if anything changes. It defaults to true. It depends on your situation which setting fits best. If your error messages are displayed in a question-specific live region, this might make sense. If error messages for the whole form live in one live region, it may be a bit too much.


Instead of manually setting up a live region, you can also give the error are a role=alert:

<div role="alert">
<!-- insert error messages here -->

This turns it into an assertive live region automatically, with aria-atomic set to true.

Focus management

When inserting a new error message

There is no need to do anything with focus after you have inserted an error message. When you insert it as the user tabs to the next field, they will likely have focused the next field. Intercepting TAB to steal their focus and bring it back to the error message would result in an unexpected experience for the user. I would consider this an anti-pattern.

When preventing submit, because there are still errors

If you have prevented submit, because there are still errors on the page, it could be useful to send focus to a list of errors that you have prepared at the start of the form. Bonus points if each item on that list is a link to the invalid item, to make it easier to correct.

Note that if you use this approach, it may conflict with your assertive live regions, as they will get read out before your list of errors. It may be better in this case to choose between the two approaches.


The above is a quite technical approach that optimises the situation for screenreader users. Another accessibility feature that can be optimised and will improve things for all of your users is the use of language.

Whether your form is complex or simple, make sure your error messages are easy to understand. Use clear and concise language. Be helpful in explaining what is wrong and how it can be fixed. This aids the user to complete the form as smoothly as possible.

In summary

In this post, I have discussed three ideas to improve the accessibility of dynamically added error messages:

Any feedback is welcome!

Thanks to Anneke and Matijs for proofreading and Krijn for feedback

Comments & mentions (27)

Ischa Gast 08 Apr 2017 11:08:19

[aria-invalid] { border-color: red; }

The color red won’t pass the wcag 2 contrast test. If you use the color darkred it’s all good :)

Hidde 09 Apr 2017 18:56:53

Haha, good point! It depends on the background color though, with #000 you would be good to go

Cezary Tomczyk 04 May 2017 06:44:08

I’d go with aria-live=“polite” as “assertive” breaks the current speech. But that’s just my preferences.

HHentry 18 Jul 2017 23:22:06

[aria-invalid] { border-color: red; } will be lost for the high colour contrast mode. What is the alternative you recommend?
I prefer a thicker outline as well.

Devarshi 20 Jun 2019 16:33:33

Nice example—completely agree; we don’t need server side validation on submit and then present an accessible error handling method.
Did anyone experience how JAWS 2019 on Chrome 75, when speaking the next element, also speaks the aria-live region of the previous field. This type of interaction could be confusing to blind users—they need to be able to clearly orient themselves to the field in error when keyboard focus is in another field. Correct me if someone got different results in their testing.

Paul Kinlan likes this
Danny de Vries likes this
Nikolay Cholakov🙏4❤ likes this
Benjy Stanton likes this
Steve Lee likes this
bebsico likes this
Jon Gibbins likes this
accessibility= reposted this
Nikolay Cholakov🙏4❤ reposted this
Scroll UK replied: There's some excellent advice on field error accessibility in this. #a11y…
Scroll UK replied: There's some excellent advice on field error accessibility in this. #a11y…
Gemma Gentles replied: Use role=alert when building forms to make sure the screen reader alerts the user that they have filled in the input field incorrectly. Great tips on #a11y and improving your #webdevelopment here…
Emmanuel DEMEY replied: How to make inline error messages accessible…
Amy Leak replied: Great article about how to make inline error messages more accessible… #accessibility #ux
Scroll UK replied: There's some excellent advice on field error accessibility in this.

brux replied:…
Jon Gibbins replied: Totally love this! I’ve been thinking about making a move to a static site generator having used Markdown for audit reports for a couple of years now.
Steve Lee replied: what starter projects are for!!!
Steve Lee replied: cc @eleven_ty
ppk 🇪🇺 replied: I bet JavaScript actually makes a site more accessible in this case.
Hidde replied: Yes, more resiliently I would say. Doing it in CSS is a maybe works, maybe not (depends on what the browser accessibilty tree decides to do with it), live regions are pretty well supported
ppk 🇪🇺 replied:
If you use CSS for form validation chances are you're going to be "smart" about it. Let's not go there. My book will advise to use JavaScript primarily, and CSS only as an extra layer of usability.
Leave a comment
Posted a response to this?

This website uses Webmentions. You can manually notify me if you have posted a response, by entering the URL below.