Focus – Change of context
A change of context refers to dynamic changes or updates that happen when an interactive element receives focus on the page using keyboard Tab key.
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.
Instead of triggering the dynamic changes on activating Space/Enter keys, the changes get triggered on receiving keyboard focus.
An example of dynamic change could be, Menus that get expanded as soon as it receives focus.
- We SHOULD NOT change context on focus since it may disorient users.
- Ensure that the focus remains on the element itself.
-
If any dynamic change happens, users should be informed about it in advance or SHOULD ONLY
happen on user interaction.
- For example, once an element receives focus, the user activates the element by pressing Enter/Space, a change of context can happen, but it needs to be informed in advance to users.
- Similarly, when a user is typing within a comment box that consists of certain character limitation, the dynamic change of characters must be programmatically defined as well.
In the case of menus, if they get expanded on focus, this can be considered as valid unless there is change of focus as well.
More information on handling the dynamic changes can be found in the following components.
Focus management when change of context occurs benefits majorly the below users.
- People with cognitive disabilities
- People with visual disabilities
- People using speech input
- People with limited dexterity
- People using keyboard only
<div class="main-containor">
<h3>Change of content </h3>
<div id="example1">
<h4>Method 1: change of content by InnerHTML</h4>
<div>
<label for="myTextarea">Comment</label>
<textarea id="myTextarea" maxlength="250"></textarea>
<div id="counter">0/250</div>
</div>
</div>
<div id="example2">
<h4>Method 2: change of content by AppendChild</h4>
<h5>Change Password</h5>
<div id="alert-container"></div>
<div class="autocomplete">
<form id="contact-us" method="post">
<label for="password">Password</label>
<input name="password" id="password" type="password" autocomplete="current-password">
<label for="cpassword">Confirm Password</label>
<input name="cpassword" id="cpassword" type="password" autocomplete="current-password">
<input type="button" class="alert-button" id="alert-button" name="alert-button" value="Reset Password" onclick="checkPasswords()">
</form>
</div>
</div>
<div id="example3">
<h4>Method 3: change of content by SetTimeout</h4>
<div id="upload-container">
<label for="file-input" id="upload">Upload Resume</label>
<input type="file" id="file-input">
<div class="progress-bar-alert">
<button id="upload-button">Upload<i class="fa fa-circle-o-notch fa-spin hidden" id="fa"></i></button>
</div>
</div>
<div class="visuallyhidden"><span aria-live="polite"></span></div>
</div>
</div>
#myTextarea {
height: 100px;
width: 250px;
resize: none;
}
#counter {
color: rgb(0, 0, 0);
font-size: 18px;
}
:focus {
outline: .10em solid #000000;
}
#alert-btn {
background-color: #003057;
border: 2px solid #000;
border-radius: 10px;
color: #fff;
padding: 0.35rem 1rem;
margin: 0 2rem 0 0;
font-size: 1rem;
}
#alert-btn:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5);
}
.alert {
position: relative;
margin-top: 20px;
padding: 1rem;
color: white;
font-size: 1.2rem;
text-align: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
z-index: 100;
}
.success {
background-color: #28a745;
}
.error {
background-color: #dc3545;
}
.success-text {
color: #009b24;
font-size: 1rem;
}
.closebtn {
margin-left: 1px;
color: white;
font-weight: bold;
float: right;
font-size: 22px;
line-height: 22px;
cursor: pointer;
transition: 0.3s;
}
.closebtn {
border: none;
background: transparent;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.hidden {
display: none;
}
#upload-button {
background-color: #003057;
border: 2px solid #000;
border-radius: 10px;
color: #fff;
padding: 0.35rem 1rem;
margin: 1rem 2rem 0 0;
font-size: 1rem;
}
h1 {
margin-top: 2%;
}
.fa {
margin-left: 6px;
}
.alert-button {
background-color: #003057;
border: 1px solid #000;
color: #fff;
padding: 5px 10px;
text-align: center;
text-decoration: none;
display: block;
font-size: 1rem;
border-radius: 5px;
cursor: pointer;
margin: 1rem auto;
width: 45%;
}
.alert-button:hover {
background-color: #037d76;
}
.alert-button:focus, #upload-button:focus {
outline: .15em solid #000000;
outline-offset: 1px;
}
.closebtn:focus {
outline: .08em solid #000000;
outline-offset: 1px;
}
.autocomplete {
position: relative;
max-width: 334px;
width: 100%;
}
label {
display: block;
margin-top: 1rem;
font-size: 1.1rem;
}
input[type="password"] {
display: block;
margin-bottom: 0.2rem;
padding: 0.8rem;
border: 1px solid #8e8e8e;
line-height: 1.15;
width: 90%;
border-radius: 4px;
}
.visuallyhidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
font-size: 14px;
white-space: nowrap;
}
#alert-container {
width: 50%;
}
@media screen and (max-width: 600px) {
#alert-container {
width: 100%;
}
}
.main-containor {
margin: 4rem 1rem;
}
const textarea = document.getElementById("myTextarea");
const counter = document.getElementById("counter");
textarea.addEventListener("input", () => {
const count = textarea.value.length;
counter.innerHTML = `${count}/250`;
});
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('cpassword');
function checkPasswords() {
const password = passwordInput.value;
const confirmPassword = confirmPasswordInput.value;
if (password === '' || confirmPassword === '') {
showAlert('Please fill in all fields.', 'error');
} else if (password === confirmPassword) {
showAlert('Password Reset Succesfully!', 'success');
} else {
showAlert('Password and confirm password do not match!', 'error');
}
}
const alertContainer = document.getElementById('alert-container');
function showAlert(message, type) {
alertContainer.innerHTML='';
const alertDiv = document.createElement('div');
alertDiv.setAttribute('role', 'alert');
alertDiv.setAttribute('aria-live', 'polite');
alertDiv.setAttribute('aria-atomic', 'true');
alertDiv.classList.add('alert');
alertDiv.classList.add(type);
const alertText = document.createTextNode(message);
alertDiv.appendChild(alertText);
const closeButton = document.createElement('button');
closeButton.classList.add('closebtn');
closeButton.setAttribute('aria-label','close alert')
closeButton.innerHTML = '× ';
closeButton.addEventListener('click', () => {
alertDiv.parentNode.removeChild(alertDiv);
console.log(passwordInput);
passwordInput.focus();
});
alertDiv.appendChild(closeButton);
alertContainer.appendChild(alertDiv);
closeButton.focus();
}
function showSpinner() {
let spinner = document.getElementById("fa");
spinner.classList.remove("hidden")
setTimeout(function() {
spinner.classList.add("hidden");
const span = document.createElement('span');
span.classList.add('success-text')
span.textContent = 'File Uploded Successfully';
const parentElement = labelElement.parentNode;
parentElement.insertBefore(span, labelElement);
}, 5000);
}
function hiddenmsg() {
visuallyhidden.innerHTML='File Uploading';
setTimeout(function() {
visuallyhidden.innerHTML='';
}, 5000);
}
const button = document.querySelector('#upload-button');
const input = document.getElementById("file-input");
const labelElement = document.getElementById('upload');
const visuallyhidden = document.querySelector('.visuallyhidden span')
button.addEventListener('click', (e) => {
if (input.files.length > 0) {
showSpinner();
hiddenmsg();
} else {
alert('Please select a file');
}
});