JAVASCRIPT
Implement a Focus Trap for Modals
Enhance accessibility by implementing a focus trap in modals, ensuring keyboard navigation stays within the modal's boundaries.
document.addEventListener('DOMContentLoaded', () => {
const openModalBtn = document.getElementById('openModal');
const closeModalBtn = document.getElementById('closeModal');
const modal = document.getElementById('myModal');
const firstFocusableElement = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
let currentlyFocusedElement = null;
function openModal() {
modal.style.display = 'block';
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('role', 'dialog');
currentlyFocusedElement = document.activeElement;
firstFocusableElement && firstFocusableElement.focus();
document.addEventListener('keydown', handleKeyDown);
}
function closeModal() {
modal.style.display = 'none';
modal.removeAttribute('aria-modal');
modal.removeAttribute('role');
currentlyFocusedElement && currentlyFocusedElement.focus(); // Return focus to element that opened the modal
document.removeEventListener('keydown', handleKeyDown);
}
function handleKeyDown(e) {
if (e.key === 'Escape') {
closeModal();
return;
}
if (e.key === 'Tab') {
e.preventDefault(); // Stop default tab behavior
const focusable = Array.from(modal.querySelectorAll(focusableElements));
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey) { // Shift + Tab
if (document.activeElement === first) {
last.focus();
} else {
const index = focusable.indexOf(document.activeElement);
if (index > 0) focusable[index - 1].focus();
}
} else { // Tab
if (document.activeElement === last) {
first.focus();
} else {
const index = focusable.indexOf(document.activeElement);
if (index < focusable.length - 1) focusable[index + 1].focus();
}
}
}
}
openModalBtn.addEventListener('click', openModal);
closeModalBtn.addEventListener('click', closeModal);
// Close modal if clicked outside (optional, but common for modals)
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
});
});
/*
HTML structure example:
<style>
#myModal {
display: none;
position: fixed;
z-index: 1001;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
padding-top: 50px;
}
.modal-content {
background-color: #fefefe;
margin: 5% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
}
</style>
<button id="openModal">Open Modal</button>
<div id="myModal">
<div class="modal-content">
<h2>Modal Title</h2>
<p>This is modal content. Try tabbing!</p>
<input type="text" placeholder="Input 1">
<button>Another Button</button>
<a href="#">A Link</a>
<input type="text" placeholder="Input 2">
<button id="closeModal">Close</button>
</div>
</div>
*/
How it works: This snippet implements an essential accessibility feature: a 'focus trap' for modals. When the modal opens, focus is programmatically set to its first focusable element. A 'keydown' listener on the document intercepts Tab key presses. If the user tabs past the last focusable element in the modal, focus is returned to the first, and vice-versa for Shift+Tab, ensuring keyboard navigation is confined within the modal. Escape key closes the modal and returns focus to the element that opened it.