Introduced in HTML5, the required
attribute conveys that a particular form control or grouping of controls must have a valid value on form submission. For some controls, a valid value may be a boolean (radios and checkboxes), a non-empty string (standard text fields or selects), or a specifically formatted entry (email, number, date, etc.).
Now it’s true that fully accessible client-side validation isn’t available across all browsers. This has led some to believe that the required
attribute has poor accessibility support, and that we should be using aria-required
instead. However, if you’re looking to indicate that a form control is required, the required
attribute will do that quite well.
Breakdown of support
Testing with modern browsers and screen readers, across different devices and operating systems, the required
attribute produces a “required” announcement for all tests, with the exception of using TalkBack with Firefox and Chrome on Android.
(Unfortunately, even using aria-required="true"
on a form control did not help TalkBack announce it as “required.”)
Windows 10 with JAWS 18 to 2019, and NVDA 2018.4.1
Firefox (65) with JAWS and NVDA will announce a required field as “required” and “invalid” by default. If you add aria-invalid="false"
to the element, Firefox will still announce “invalid” when the field is empty, until a valid entry has been made.
IE11 with JAWS and NVDA will announce a required field as “required” by default, but neither pairing will announce it as “invalid.” Using aria-invalid
to communicate state will rectify the lack of a native “invalid” announcement.
Edge with JAWS announces “required” by default, but will not announce a required field as being “invalid.” Using aria-invalid
to communicate state will rectify the lack of a native “invalid” announcement.
Chrome (72) with NVDA and JAWS, and Edge with NVDA and Narrator will announce a required field as “required” and “invalid” by default. Entering content into a field will negate the native “invalid” announcement, but sometimes prematurely. (For example, entering a single letter into an email field would still not pass proper validation.) If adding aria-invalid="false"
to a field by default, “invalid” will not be announced.
macOS 10.14.2 with VoiceOver
Safari (12.0.2) will announce a required field as “required” by default. Correctly announcing a field as “invalid” will depend on the type. (For example, entering a single letter into an email field will not trigger an invalid state, but entering a letter into a number field will.) Using aria-invalid
can help correctly convey state based on one’s validation script.
Chrome (71) and VoiceOver will announce a required field as “required” and “invalid” by default. Entering content into a field will negate the invalid announcement, but sometimes the field may still be invalid (e.g. entering a single letter into an email field). If adding aria-invalid="false"
to an field by default, “invalid” will not be announced.
iOS 12.1.2 with VoiceOver
Safari will announce a required field as “required” by default. Using aria-invalid
will allow for fields to be announced as “invalid” when necessary.
Android 8.1 with TalkBack 7.2
Neither Firefox (63.0.2) nor Chrome (70) will announce fields as “required” or if they contain invalid entries. Use of ARIA attributes does not help produce the expected announcements.
Review the tests yourself
If you want to play around with the markup that was used for testing, you can check out the following CodePens:
required
attribute tests with basic client-side validation- Testing
required
attribute default announcements (with no validation or ARIA) - Testing
aria-required
for comparison
Properly announcing invalid state
While using aria-required
isn’t necessary to help indicate the importance of a form control, there are still inconsistencies with the announcements of invalid states that developers will need to address. Fortunately, the attribute aria-invalid
can be used to help with just that.
To stop form controls from announcing as invalid by default, one can add aria-invalid="false"
to any necessary element.
<label for="n">
Name:
</label>
<input id="n" required aria-invalid="false">
When its time to mark a form control as invalid, the attribute’s value should be set to “true”. Ideally, a control should be set to invalid only after a required control has been focused, and then blurred, without a proper string being entered.
If writing a script to check for input validity while a control is still focused, flagging a control as invalid should only happen after a few seconds have passed without a keypress event being fired. People may find it frustrating, and potentially even confusing, if they receive feedback that their entry is invalid while they’re typing.
aria-invalid
quirks:
Note, that even when setting a form control to aria-invalid="false"
, Firefox will ignore this attribute and screen readers will still announce “invalid” when focus first entering an empty required form control.
Providing context to invalid controls
To communicate why a control is being announced as invalid, form controls should have associated error messages to help people correct invalid entries. While we can’t, and often we don’t, rely on browser’s client-side validation to be accessible, there are ways to provide accessible inline error messages.
First, suppress the browser’s default validation messages by adding the novalidate
attribute to the wrapping form
element. Now you’ll be in complete control to implement a custom client-side validation script, and helpful messaging.
Next, for any required form controls that could need inline error messaging, provide each with an aria-describedby
attribute, that points to an empty element in the document (ideally directly after the form control in the DOM). If the control becomes invalid, this element would then be populated with a concise error message. When the value of the form control is no longer invalid, the associated element should become empty, along with the control having its aria-invalid
attribute removed, or set to “false”.
<label for="n">
Name:
</label>
<input id="n" required aria-describedby="n_msg" aria-invalid="true">
<span id="n_msg">Please enter your name.</span>
Feature or a bug?
Interestingly, when populating an element that is the target of aria-describedby
, Chrome (specifically on Windows) will treat it as a live region. JAWS and NVDA will immediately announce the content that is populated into the element.
Android Firefox description gap
If using Firefox (64.0.2) on Android, you’ll find TalkBack doesn’t announce aria-describedby
content when a form control is focused. This is not an issue with Chrome on Android. Bug filed for Firefox on Android.
Wrapping up
When using the required
attribute on form controls, you’re going to want to keep the following in mind:
- Excluding Android browsers paired with TalkBack, all other tested screen reader and browser pairings announce a form control as “required” when using the
required
attribute. - Use the
novalidate
attribute on aform
to disable browsers’ client-side validation, and instead implement custom validation scripts and accessible error messaging. - To ensure most screen readers won’t default to announcing required form controls as invalid, use
aria-invalid="false"
. - Update the
aria-invalid
attribute to “true” if the current value (or lack thereof) of a required control does not pass validation. - Use
aria-describedby
on required form controls to point to an element that will contain any necessary inline error messaging. - Be patient before marking a form control as invalid and displaying an inline error message. Ideally wait until the control has lost focus, or there has been an adequate delay in key presses.
And finally, perform your own testing! Modern browsers and screen readers are updated quite frequently. That means that it’s quite possible that support for features could be added without you even noticing. Or on the flip side, bugs or unique heuristics could be introduced, which you may also need to be accounted for.
Comments
A little side-note I’d add: if the validation happens server-side, and you’re then bouncing users back to the form with invalid fields marked with
aria-invalid="true"
, it’s worth also removing that attribute dynamically once the user has changed anything relating to that field, as otherwise even if they correct their error, AT will still announce it as invalid, even though at that point it may well be valid (but we won’t know until it’s sent back to the server for validation). So, something like(using inline event handler here for compactness)
Thank you for the note/addition Patrick!
Based on Patrick’s suggestion, and from a progressive enhancement standpoint, could those values be set on load by JavaScript? I’m imagining a situation where the aria-invalid attribute is generated server-side but if JavaScript was disabled or not loaded, like on a slow connection, it would lead to confusion if the attribute isn’t removed/value isn’t changed.
Is that worrying too much about an edge case here, or is there an approach that would be as accessible as possible by default without requiring JavaScript?
Hey Garrett,
I’d definitely recommend that any ARIA attributes used to modify the announcements only be set to elements when JavaScript is enabled, exactly for the reason you mentioned. Without using aria-invalid, the fields would just fall back to the default announcements that each browser would expose.
set them server-side to
data-aria-invalid="true"
or something, then on load change the attributes to justaria-invalid="true"
… yeah, that could be done.Hey Scott!
Wonderful checklist at the end – I’ll be checking that a few times. Out of interest, do you know how
aria-describedby
compares toaria-errormessage
? Is it worth using one over the other?Cheers
James
Hey James, thanks for reading.
Last I checked, which would be late 2017/early 2018, support for aria-errormessage wasn’t really there to make it a viable option. I’m unaware of support changing since last I checked, but seems like it might be due to give it another look.
Alas, I think the support still isn’t there. I managed to find a couple of tickets open for
aria-errormessage
support.Seems like
aria-describedby
is still the better option. Thanks for replying 🙂The Firefox team fixed the bug with
aria-describedby
in Firefox 67 🙂