CAPTCHA
CAPTCHA is a mechanism which runs a simple test to confirm whether a user using the website is human or not.
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.
- CAPTCHA CAN be image-based, audio-based, text based and so on.
- In ideal cases, avoiding CAPTCHA is the safest way of ensuring an accessible user experience for assistive technology users.
-
An alternative for CAPTCHA CAN be human logic questions.
- In this case, using a confirmation page as second step verification OR multifactor authentication using text message or email helps.
- Unique and descriptive accessible names SHOULD be specified for the interactive controls present in the CAPTCHA. For more information, refer to Label placement and structure component.
- The contrast requirement of 3:1 ratio MUST be met with the adjacent colors for the interactive controls in default, focus and hover states.
- The contrast requirement of 3:1 ratio MUST be met with the adjacent colors for the custom focus indicator of the interactive controls.
- If image-based CAPTCHA is given, an alternate form such as audio-based or question-based CAPTCHA SHOULD be given.
- CAPTCHA images SHOULD be accessible. For more information, refer to the Images component.
- The text displayed within the CAPTCHA image SHOULD pass the minimum color contrast ratio requirement of 4.5:1 with the background color.
- The CAPTCHA audio SHOULD NOT interfere or overlap with the screen reader audio.
- There should be a mechanism to replay the audio for CAPTCHA.
A well-defined CAPTCHA benefits majorly the below users.
- People with visual disabilities
- People with cognitive disabilities
- People using speech input
- People with limited dexterity
- People using keyboard only
Note: This CAPTCHA example/code is only for demonstration purposes. DO NOT implement a CAPTCHA using JavaScript as this will defeat the purpose of the CAPTCHA by exposing the correct answer to a bot.
<form id="contact-us" onsubmit="return validateForm()" method="post">
<p style="font-weight: bold;">
Fields marked with asterisk (<span class="span">*</span>) are mandatory.
</p>
<div class="form-control">
<label for="full_name">Full Name:<span class="span">*</span></label>
<input type="text" name="full_name" id="full_name" autocomplete="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 (numbers only):<span class="span">*</span></label>
<input type="tel" name="phone" autocomplete="tel" id="phone" aria-required="true" maxlength="15">
</div>
<div class="form-control captcha-container">
<label for="captcha-input">
Security Question<span class="span">*</span>:
</label>
<span id="captcha-math"></span>
<input type="text" id="captcha-input">
<button aria-label="Refresh the captcha" id="captcha-refresh" type="button">
<i class="fa fa-refresh" style="font-size:18px"></i>
</button>
</div>
<input name="submit" id="submit" type="submit" value="Submit">
</form>
#captcha-math {
font-size: 18px;
margin: 10px;
}
#captcha-refresh {
padding: 5px 10px;
background-color: #007BFF;
color: #fff;
border: none;
cursor: pointer;
}
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;
display: block;
}
.form-control { margin-bottom: 0.8rem; }
#error {
color: #af0404;
font-weight: 600;
}
#errlist { color: #af0404; }
#errorderlist li { margin: 8px 0; }
@media screen and (max-width: 600px) {
#contact-us { width: 100%; }
input,
textarea {
width: 90%;
}
}
const captchaMath = document.getElementById('captcha-math');
const captchaInput = document.getElementById('captcha-input');
const captchaRefresh = document.getElementById('captcha-refresh');
function generateRandomMathProblem() {
const operators = ['+', '-', '*'];
const operator = operators[Math.floor(Math.random() * operators.length)];
let num1 = Math.floor(Math.random() * 10) + 1;
let num2 = Math.floor(Math.random() * 10) + 1;
const mathProblem = `${num1} ${operator} ${num2}`;
let answer;
switch (operator) {
case '+':
answer = num1 + num2;
break;
case '-':
answer = num1 - num2;
break;
case '*':
answer = num1 * num2;
break;
}
return { problem: mathProblem, answer };
}
function refreshMathCaptcha() {
const { problem, answer } = generateRandomMathProblem();
captchaMath.textContent = problem;
captchaInput.value = '';
captchaInput.dataset.answer = answer;
}
captchaRefresh.addEventListener('click', refreshMathCaptcha);
function createFieldErrorMessage(fieldName, errIndex, errMsg) {
let span_field;
let elem_event_name;
let err_summary_list;
let err_summary_anchor;
// let err_sum_list;
span_field = document.createElement('span');
span_field.setAttribute('class', 'errText');
span_field.setAttribute('id', 'err_' + errIndex);
span_field.innerHTML = 'Error: ' + errMsg;
elem_event_name = document.getElementById(fieldName);
elem_event_name.setAttribute('aria-describedby', 'err_' + errIndex);
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', '#' + fieldName);
err_summary_anchor.setAttribute('id', 'errlist');
err_sum_list = err_summary_anchor.innerHTML = ' ' + errMsg;
document.getElementById("errorderlist").appendChild(err_summary_list);
}
function validateForm() {
let i = 0;
const fullName = document.getElementById('full_name').value;
const email = document.getElementById('email').value;
const phone = document.getElementById('phone').value;
const capt = captchaInput.value;
const userAnswer = parseInt(captchaInput.value, 10);
const correctAnswer = parseInt(captchaInput.dataset.answer, 10);
let focuss ="";
// let focus = '';
document.querySelectorAll('#errorderlist li').forEach(function(a) {
a.remove();
});
document.querySelectorAll("span.errText").forEach(function(a) {
next_sibling = a.nextSibling;
next_sibling.removeAttribute('aria-describedby');
next_sibling.removeAttribute('aria-invalid');
a.remove();
});
if (fullName == "") {
createFieldErrorMessage('full_name', i, 'Please enter your full name.');
if (focuss == "") {
focuss = 'full_name';
}
i++;
} else if ((isNaN(fullName)) == false) {
createFieldErrorMessage('full_name', i, 'Name cannot include numbers.');
if (focuss == "") {
focuss = 'full_name';
}
i++;
}
if (email == "") {
createFieldErrorMessage('email', i, 'Please enter your email address.');
if (focuss == "") {
focuss = 'email';
}
i++;
}
if ((isNaN(phone)) == false) {
createFieldErrorMessage('phone', i, 'Phone number can only include numbers.');
if (focuss == "") {
focuss = 'phone';
}
i++;
}
if (capt) {
if (isNaN(userAnswer) || userAnswer !== correctAnswer) {
createFieldErrorMessage('captcha-input', i, 'Incorrect answer for security question.');
if (focuss == "") {
focuss = 'captcha-input';
}
i++;
refreshMatchCaptcha();
}
} else {
createFieldErrorMessage('captcha-input', i, 'Answer to security question cannot be blank.');
if (focuss == "") {
focuss = 'captch-input';
}
i++;
}
if (i != 0) {
document.getElementById('error').innerHTML = "Following form consists of "+ i +" error(s). Please fix the errors and try again.\n
";
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;
}
}
refreshMathCaptcha();