Error Handling with WAI-ARIA
Form validation messages or error messages help users rectify their mistakes and submit the form successfully.
Error messages SHOULD be clear, descriptive and easily available for all users to identify their presence. When a screen reader user encounters an input element in an error state, this information needs to be announced alongside its label text.
Error messages SHOULD contain instructions on what valid input is acceptable wherever possible. For example, for the Date field, the correct format of date to be entered can be included in the error message. This aligns users as to what is wrong in their input.
For example, date field is expected to be in MM/DD/YYYY format.
Error handling CAN be managed using following approaches:
NOTE:
- New to accessibility or uncertain of requirements, it will be helpful to review all sections below.
- Already familiar with requirements, skip to the “Working Example” section for sample HTML, CSS and JavaScript (when needed), along with a working demo.
-
Define an error summary at the start of the form.
<div id="error" tabindex="-1" role="alert" style="display: block"> <p> Following form consists of 2 error(s). Please fix the errors and try again. </p> </div>
-
Specify
role="alert"
for the container that consists of the error summary. -
Focus SHOULD be set on the container that consists of the error summary when form
is submitted with inaccurate values.
-
Use JavaScript
.focus()
method orHTMLElement.focus()
method to set focus on the container. -
Assign
tabindex="-1"
attribute on the container since the container is a non-interactive element.
Tip: Usingtabindex="-1"
also makes the implementation robust across different environments. -
DO NOT assign
tabindex="0"
on the container as it will make the container focusable causing another accessibility issue.
-
Use JavaScript
- DO NOT place the error summary in between the form.
-
In addition to the error summary, a list of error messages SHOULD be provided as
well.
- Place the list of error messages after the error summary in the DOM.
-
Define the list of error messages within an unordered list structure using
<ul>
and<li>
tags. - Define the list of error messages as bookmark links using
<a>
tag and a valid href attribute that consists of targeted element id attribute value. That is, when any of the bookmark links is activated, the focus jumps to the respective field with error. -
The link text should be descriptive enough to understand the purpose.
For example,<!--suppress XmlDuplicatedId, XmlDuplicatedId --> <div id="error" tabindex="-1" role="alert" style="display: block"> <p> Following form consists of 2 error(s). Please fix the errors and try again. </p> </div> <ul id="errorderlist"> <li><a href="#email" id="errlist">Please enter your email.</a></li> <li> <a href="#phone" id="errlist">Please enter your phone number.</a> </li> </ul> … <div class="form-control"> <label for="email">Email:<span class="span">*</span></label> <input type="email" id="email" aria-invalid="true" autocomplete="email" name="email" aria-required="true" aria-describedby="err_0"> <span class="errText" id="err_0">Error: Please enter email.</span> </div> …<div class="form-control"> <label for="phone">Phone:<span class="span">*</span></label> <input type="text" id="phone" aria-invalid="true" autocomplete="tel" name="phone" aria-required="true" aria-describedby="err_1"> <span class="errText" id="err_1">Error: Please enter email.</span> </div> …
-
Place the inline error messages below the respective form fields.
- Specify “Error” keyword at the start of error message.
-
Associate the error messages using
aria-describedby
attribute.-
Specify
aria-describedby
attribute for the respective<input>
element. -
Specify unique
id
attribute for the container in which error text is specified for respective form fields. -
Programmatically, the value of
aria-describedby
attribute SHOULD be referenced to the id attribute of the element containing the error text. -
Define
aria-invalid
attribute for form fields to programmatically convey that the value entered is incorrect.
Note: Whenaria-invalid
is used along with thearia-required
attribute,aria-invalid
should not be set totrue
before the form is submitted – only in response to validation. -
Use JavScript
.focus()
method orHTMLElement.focus()
method to set focus on the first field with error when the form is submitted with inaccurate data.
Note: When there is error summary along with the list of error messages as bookmark link at the start of form, ensure that the programmatic focus is set to the container in which the error summary is structured. -
In this case, DO NOT specify
tabindex="-1"
attribute on the targeted field with error as it will remove the respective field from tabbing order. -
DO NOT specify live region on the container of error text of respective form
field.
Tip: Multiple live regions are not recommended as the required information might not get announced and the live regions would interfere with each other.
For example,<label for="email">Email:<span class="span">*</span></label> <input type="email" id="email" autocomplete="email" name="email" aria-required="true" aria-invalid="true" aria-describedby="err_0"> <span class="errText" id="err_0">Error: Please enter email.</span>
-
Specify
Textual contrast:
-
The error message text MUST have sufficient color contrast with its background.
- Minimum requirement of 4.5:1 MUST be met for the standard text size.
- Minimum requirement of 3:1 MUST be met for larger text size i.e., 14pts with bold weight or more than 18 pts.
Non-text contrast:
- If case icons are used (e.g., ⚠) to identify the field with error along with a textual message, the icon MUST have a sufficient color contrast ratio of 3:1 with its background.
Color Alone:
- Avoid using only color to indicate field with error. There MUST be a textual error message that describes how to fix the field with error.
A form with well-defined error handling benefits majorly the below users.
- People with cognitive disabilities
- People using speech input
- People with limited dexterity
- People using screen readers
- People using keyboard only
<div id="error" tabindex="-1" role="alert">
</div>
<ul id="errorderlist"></ul>
<form id="contact-us" onsubmit="return checkError(this)" method="post">
<p>
<strong>Fields marked with asterisk (<span class="span">*</span>)
are mandatory.</strong>
</p>
<div class="form-control">
<label for="firstname">
First Name:<span class="span">*</span>
</label>
<input type="text" name="firstname" id="firstname"
autocomplete="given-name" aria-required="true">
</div>
<div class="form-control">
<label for="lastname">
Last Name:<span class="span">*</span>
</label>
<input type="text" name="lastname" id="lastname"
autocomplete="family-name" aria-required="true">
</div>
<div class="form-control">
<label for="email">Email:<span class="span">*</span></label>
<input type="email" id="email" autocomplete="email" name="email"
aria-required="true">
</div>
<div class="form-control">
<label for="phone">Phone:<span class="span">*</span></label>
<input type="tel" name="phone" autocomplete="tel" id="phone" aria-required="true" maxlength="14">
</div>
<input name="submit" id="submit" type="submit" value="Submit">
</form>
body {
font-family: Helvetica,Verdana,sans-serif;
font-size: 1rem;
}
input, textarea {
display: block;
margin-bottom: 0.2rem;
padding: 0.8rem;
border: 1px solid #8e8e8e;
line-height: 1.15;
width: 40%;
border-radius: 4px;
}
input[type=submit] {
background-color: #003057;
border: 2px solid #000;
border-radius: 10px;
color: #fff;
display: inline-block;
line-height: 1.15;
padding: 0.35rem 1rem;
width: auto;
text-align: center;
text-decoration: none;
margin: 0 2rem 0 0;
font-size: 1rem;
vertical-align: middle;
}
input[type=submit]:hover,
input[type=submit]:focus {
background-color: #068379;
cursor: pointer;
text-decoration: none;
}
.span { color: #be0000; }
.errText { color: #af0404; }
.form-control { margin-bottom: 0.8rem; }
#error {
color: #af0404;
font-weight: 600;
}
#errlist { color: #af0404; }
#errorderlist li { margin-top: 5px; }
@media screen and (max-width: 600px) {
#contact-us { width: 100%; }
input, textarea { width: 90%; }
}
function validateNumber(input) {
let re = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im;
return re.test(input)
}
function isNumber(evt) {
evt = (evt) ? evt : window.event;
let charCode = (evt.which) ? evt.which : evt.keyCode;
if (charCode > 31 && (charCode < 48 || charCode > 57) && charCode != 45 && charCode != 40 && charCode != 41) {
evt.preventDefault();
}
// return true;
}
function checkError(data) {
let i = 0;
let phone = data['phone'].value;
let focuss ="";
let span_field;
let elem_event_name;
let err_summary_list;
let err_summary_anchor;
let err_sum_list;
document.querySelectorAll("#errorderlist li").forEach(function(a) {
a.remove();
});
document.querySelectorAll("span.errText").forEach(function(a) {
a.previousSibling.removeAttribute('aria-describedby');
a.previousSibling.removeAttribute('aria-invalid');
a.remove();
});
if (data['firstname'].value == "") {
span_field = document.createElement('span');
span_field.setAttribute('class','errText');
span_field.setAttribute('id','err_'+i);
span_field.innerHTML = 'Error: Please enter your first name.';
elem_event_name = document.getElementById('firstname');
elem_event_name.setAttribute('aria-describedby','err_'+i);
elem_event_name.parentNode.insertBefore( span_field, elem_event_name );
elem_event_name.setAttribute('aria-invalid','true');
err_summary_list = document.createElement('li');
err_summary_anchor = document.createElement('a');
err_summary_list.appendChild(err_summary_anchor);
err_summary_anchor.setAttribute('href','#firstname');
err_summary_anchor.setAttribute('id','errlist');
err_sum_list = err_summary_anchor.innerHTML = " Please enter your first name.";
document.getElementById("errorderlist").appendChild(err_summary_list);
if (focuss == "") {
focuss = "firstname";
}
i++;
} else if ((isNaN(data['firstname'].value)) == false) {
span_field = document.createElement('span');
span_field.setAttribute('class','errText');
span_field.setAttribute('id','err_'+i);
span_field.innerHTML = 'Error: Name cannot include numbers.';
elem_event_name = document.getElementById('firstname');
elem_event_name.setAttribute('aria-describedby','err_'+i);
elem_event_name.parentNode.insertBefore( span_field, elem_event_name );
elem_event_name.setAttribute('aria-invalid','true');
err_summary_list = document.createElement('li');
err_summary_anchor = document.createElement('a');
err_summary_list.appendChild(err_summary_anchor);
err_summary_anchor.setAttribute('href','#firstname');
err_summary_anchor.setAttribute('id','errlist');
err_sum_list = err_summary_anchor.innerHTML = " Name cannot include numbers.";
document.getElementById("errorderlist").appendChild(err_summary_list);
if (focuss == "") {
focuss = "firstname";
}
i++;
}
if (data['lastname'].value == "") {
span_field = document.createElement('span');
span_field.setAttribute('class','errText');
span_field.setAttribute('id','err_'+i);
span_field.innerHTML = 'Error: Please enter your last name.';
elem_event_name = document.getElementById('lastname');
elem_event_name.setAttribute('aria-describedby','err_'+i);
elem_event_name.parentNode.insertBefore( span_field, elem_event_name );
elem_event_name.setAttribute('aria-invalid','true');
err_summary_list = document.createElement('li');
err_summary_anchor = document.createElement('a');
err_summary_list.appendChild(err_summary_anchor);
err_summary_anchor.setAttribute('href','#lastname');
err_summary_anchor.setAttribute('id','errlist');
err_sum_list = err_summary_anchor.innerHTML = " Please enter your last name";
document.getElementById("errorderlist").appendChild(err_summary_list);
if (focuss == "") {
focuss = "lastname";
}
i++;
} else if ((isNaN(data['lastname'].value)) == false) {
span_field = document.createElement('span');
span_field.setAttribute('class','errText');
span_field.setAttribute('id','err_'+i);
span_field.innerHTML = 'Error: Name cannot include numbers.';
elem_event_name = document.getElementById('lastname');
elem_event_name.setAttribute('aria-describedby','err_'+i);
elem_event_name.parentNode.insertBefore( span_field, elem_event_name );
elem_event_name.setAttribute('aria-invalid','true');
err_summary_list = document.createElement('li');
err_summary_anchor = document.createElement('a');
err_summary_list.appendChild(err_summary_anchor);
err_summary_anchor.setAttribute('href','#lastname');
err_summary_anchor.setAttribute('id','errlist');
err_sum_list = err_summary_anchor.innerHTML = " Name cannot include numbers.";
document.getElementById("errorderlist").appendChild(err_summary_list);
if (focuss == "") {
focuss = "lastname";
}
i++;
}
if (data['email'].value == "") {
span_field = document.createElement('span');
span_field.setAttribute('class','errText');
span_field.setAttribute('id','err_'+i);
span_field.innerHTML = 'Error: Please enter email.';
elem_event_name = document.getElementById('email');
elem_event_name.setAttribute('aria-describedby','err_'+i);
elem_event_name.parentNode.insertBefore( span_field, elem_event_name );
elem_event_name.setAttribute('aria-invalid','true');
err_summary_list = document.createElement('li');
err_summary_anchor = document.createElement('a');
err_summary_list.appendChild(err_summary_anchor);
err_summary_anchor.setAttribute('href','#email');
err_summary_anchor.setAttribute('id','errlist');
err_sum_list = err_summary_anchor.innerHTML = " Please enter your email.";
document.getElementById("errorderlist").appendChild(err_summary_list);
if (focuss == "") {
focuss = "email";
}
i++;
} else {
let invalidcheck = 0;
let str = data['email'].value;
let filter =/^(\w+(?:\.\w+)*)@((?:\w+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if (filter.test(str)) {
let after_at = str.substring(str.indexOf('@') + 1);
let before_dot = after_at.split(".");
after_at = before_dot[0];
} else {
span_field = document.createElement('span');
span_field.setAttribute('class','errText');
span_field.setAttribute('id','err_'+i);
span_field.innerHTML = 'Error: Please enter a valid email address. For example: john@abc.com.';
elem_event_name = document.getElementById('email');
elem_event_name.setAttribute('aria-describedby','err_'+i);
elem_event_name.parentNode.insertBefore( span_field, elem_event_name );
elem_event_name.setAttribute('aria-invalid','true');
err_summary_list = document.createElement('li');
err_summary_anchor = document.createElement('a');
err_summary_list.appendChild(err_summary_anchor);
err_summary_anchor.setAttribute('href','#email');
err_summary_anchor.setAttribute('id','errlist');
err_sum_list = err_summary_anchor.innerHTML = " Please enter a valid email address. For example: john@abc.com.";
document.getElementById("errorderlist").appendChild(err_summary_list);
if (focuss == "") {
focuss = "email";
}
i++;
}
}
if (data['phone'].value == "") {
span_field = document.createElement('span');
span_field.setAttribute('class','errText');
span_field.setAttribute('id','err_'+i);
span_field.innerHTML = 'Error: Please enter your phone number.';
elem_event_name = document.getElementById('phone');
elem_event_name.setAttribute('aria-describedby','err_'+i);
elem_event_name.parentNode.insertBefore( span_field, elem_event_name );
elem_event_name.setAttribute('aria-invalid','true');
err_summary_list = document.createElement('li');
err_summary_anchor = document.createElement('a');
err_summary_list.appendChild(err_summary_anchor);
err_summary_anchor.setAttribute('href','#phone');
err_summary_anchor.setAttribute('id','errlist');
err_sum_list = err_summary_anchor.innerHTML = " Please enter your phone number.";
document.getElementById("errorderlist").appendChild(err_summary_list);
if (focuss == "") {
focuss = "phone";
}
i++;
} else if (!validateNumber(phone)) {
span_field = document.createElement('span');
span_field.setAttribute('class','errText');
span_field.setAttribute('id','err_'+i);
span_field.innerHTML = 'Error: Please enter a valid contact number.';
elem_event_name = document.getElementById('phone');
elem_event_name.setAttribute('aria-describedby','err_'+i);
elem_event_name.parentNode.insertBefore( span_field, elem_event_name );
elem_event_name.setAttribute('aria-invalid','true');
err_summary_list = document.createElement('li');
err_summary_anchor = document.createElement('a');
err_summary_list.appendChild(err_summary_anchor);
err_summary_anchor.setAttribute('href','#phone');
err_summary_anchor.setAttribute('id','errlist');
err_sum_list = err_summary_anchor.innerHTML = " Please enter a valid contact number.";
document.getElementById("errorderlist").appendChild(err_summary_list);
if (focuss == "") {
focuss = "phone";
}
i++;
}
if (i != 0) {
document.getElementById('error').innerHTML = "<p>Following form consists of "+ i +" error(s). Please fix the errors and try again.\n</p>";
document.getElementById('error').style.display = 'block';
document.getElementById('error').focus();
return false;
} else {
alert("Form Submitted Successfully");
document.getElementById("contact-us").reset();
document.getElementById('error').innerHTML="";
return false;
}
}