Mike
Mike

Reputation: 37

How to properly handle inline error messages while the user is typing (ARIA)?

We have a form on our website containing fields that are validated with JavaScript as the user types (onChange event). The error messages are shown below the field inputs in red text.

We currently use the aria-describedby and aria-invalid attributes to surface the message to NVDA. The screen-reader correctly announces when the field becomes invalid, however it doesn't announce when the field becomes valid again. This was highlighted as problematic by our testing team.

We also looked into adding role="alert" to the error field, but this appears to cause "double-speak" when the field becomes invalid - it announces the error once because of the role="alert", and again because of the aria-describedby.

What is the correct/recommended way to handle this?

Upvotes: 1

Views: 2144

Answers (1)

slugolicious
slugolicious

Reputation: 17485

You're pretty close. There are essentially two things you need to do with error messages to make them accessible to everyone (WCAG 3.3.1).

  1. announce the error message when it happens
  2. associate the error message with the field in error

It sounds like you're handling #2 via aria-describedby and aria-invalid. If the user navigates away from the field in error then navigates back, they'll hear the field's label (hopefully you have the label associated with the field via <label for="id">) and they'll hear the message associated with it as the description of the field because of aria-describedby. They will also hear the field announced as "invalid" because of aria-invalid.

For #1 you'll want to use a live region. Using role="alert" is one way to get a live region but that causes an implicit aria-live="assertive". Assertive live regions are generally not needed unless the message is so important that the screen reader needs to stop announcing whatever it was saying to announce your message. It's generally better to use aria-live="polite".

In your case, if the error is displayed as the user is typing (for example, if you have a "create password" field and you want to check conditions off as they type [upper case, lower case, number, special char, etc]), then using aria-live="polite" will allow the user to type at their normal speed and hear the characters they're typing and when they pause for a second then your message will be announced.

If instead you use aria-live="assertive" (or role="alert"), then as the user is typing, if you display the error message, the characters that the user is typing will no longer be announced because the screen reader will stop saying the characters and will instead announce your error message. Assertive messages might clear any pending announcement the screen reader was going to say so the user might not hear any other characters they typed when the error was announced so now they won't know what they typed.

From aria-live spec:

User agents or assistive technologies MAY choose to clear queued changes when an assertive change occurs.

Anyway, with all that being said, you're code should look something like this:

<label for="myid">Enter stuff</label>
<input id="myid" aria-describedby="myerror">
<div aria-live="polite" id="myerror"></div>

As the user is typing and your onChange() runs, when you discover an error, you'll set aria-invalid to true and inject the error message text into the <div id="myerror"> element and it'll be announced.

The second part of your question is when the user fixes the field. How will the user know when the error is cleared? As the user is typing and the error is corrected, you'll set aria-invalid to false and you'll clear the <div>. Sometimes when toggling the value of an ARIA attribute, the change is announced automatically. This happens for aria-expanded and aria-checked but generally does not happen for aria-invalid (although it might be nice if it did).

So if you want to tell the user that the field is not in error anymore, you'd need a second aria-live region that is visibly hidden that is used solely for announcing the "all clear" message. When you clear the error message from your <div id="myerror">, because live regions announce the change in content, your content changed from an error message to blank text, the screen reader doesn't really have anything to announce. I suppose it could say "blank" because that's the new content but that doesn't really tell the user that it's not in error anymore.

So with a second live region, you can inject it with a "no errors found" or "errors cleared" or some message your designer deems appropriate. So the code would now be:

<label for="myid">Enter stuff</label>
<input id="myid" aria-describedby="myerror">
<div aria-live="polite" id="myerror"></div>
<div aria-live="polite" id="allclear" class="sr-only"></div>

When an error is determined to be cleared, you inject the text into the <div id="allclear"> and it'll be announced. You'll also have to update your error detection code to clear the "allclear" message when an error is found, in addition to injecting into <div id="myerror"> and setting aria-invalid.

The last part is the "sr-only" class used on the "allclear". That's not a predefined class but is a common name for a class that can visibly hide an element so that sighted users don't see the element (you don't want to physically see the "all clear" message on the screen) but screen readers still have access to the text. You can find a sample definition of "sr-only" here, What is sr-only in Bootstrap 3?

I know that looks like a lot of information but once you have it all coded, it's not that bad.

As a final side note, if your error message color is pure red, #FF0000, then that won't have sufficient contrast on a white background (4:1). You'll need a slightly darker red such as #ee0000 (4.5:1). This will satisfy WCAG 1.4.3

Upvotes: 5

Related Questions