When a user opens an overlay component like a modal dialog, a dropdown menu, or a confirmation prompt, keyboard focus should remain inside that component until the user intentionally closes it. Without this constraint, pressing Tab can move focus behind the overlay and onto elements the user cannot see, creating a confusing and sometimes impossible-to-navigate experience. A focus trap solves this by intercepting keyboard navigation at the boundaries of the component and looping focus back to the opposite end.
Focus traps are not the same as “keyboard traps,” which is a negative accessibility pattern where focus gets stuck and the user has no way to escape. A well-implemented focus trap always provides at least one clear exit mechanism—typically the Escape key or an explicit close button—that returns focus to the element that triggered the component in the first place.
Why focus traps matter
Focus management is a cornerstone of keyboard accessibility. Sighted mouse users can simply click the close button on a modal, but keyboard-only users, screen reader users, and people who rely on switch devices depend entirely on the logical flow of focus. WCAG 2.1 Success Criterion 2.1.2 (No Keyboard Trap) explicitly requires that users can move focus away from any component using standard keystrokes. A focus trap that follows best practices satisfies this criterion while also meeting the spirit of SC 2.4.3 (Focus Order) by keeping the navigation sequence meaningful.
Without a focus trap:
- A keyboard user pressing Tab in a modal may land on a page element hidden behind a dimmed backdrop, with no visual or programmatic indication of where they are.
- A screen reader may announce content the user did not intend to interact with, breaking the mental model of the dialog.
- The user may be unable to return to the modal at all, effectively losing access to the dialog’s controls.
Focus traps affect every user who does not rely exclusively on a pointing device—a group that includes power users, people with motor impairments, people with low vision, and anyone navigating with assistive technology.
How focus traps work
Identifying focusable elements
When the trapped region opens, the script collects all focusable elements inside it. Focusable elements typically include <a> with an href, <button>, <input>, <select>, <textarea>, and any element with a non-negative tabindex. Elements that are hidden (display: none, visibility: hidden) or disabled are excluded.
Intercepting Tab and Shift+Tab
A keydown event listener watches for the Tab key. When the user presses Tab on the last focusable element, focus is programmatically moved to the first focusable element. When the user presses Shift+Tab on the first focusable element, focus moves to the last. This creates a seamless loop.
Providing an exit
The Escape key should close the component and return focus to the triggering element. A visible close button should do the same. Both paths release the trap and restore the page’s normal tab order.
Setting initial focus
When the modal opens, focus should move to the first interactive element inside it—or to the dialog container itself if it has tabindex="-1" and a descriptive label. This signals to assistive technology that context has changed.
Code examples
Bad example — no focus trap
In this example the modal opens but focus is never constrained. A keyboard user pressing Tab will escape the dialog and land on background content.
<div id="modal" role="dialog" aria-labelledby="modal-title" aria-modal="true">
<h2 id="modal-title">Confirm deletion</h2>
<p>Are you sure you want to delete this item?</p>
<button id="confirm-btn">Delete</button>
<button id="cancel-btn">Cancel</button>
</div>
<script>
// Modal is shown, but no focus management happens.
document.getElementById("modal").style.display = "block";
</script>
Good example — focus trap with exit via Escape
<div
id="modal"
role="dialog"
aria-labelledby="modal-title"
aria-modal="true"
tabindex="-1"
>
<h2 id="modal-title">Confirm deletion</h2>
<p>Are you sure you want to delete this item?</p>
<button id="confirm-btn">Delete</button>
<button id="cancel-btn">Cancel</button>
</div>
<script>
const trigger = document.getElementById("open-modal-btn");
const modal = document.getElementById("modal");
function openModal() {
modal.style.display = "block";
modal.focus();
document.addEventListener("keydown", trapFocus);
}
function closeModal() {
modal.style.display = "none";
document.removeEventListener("keydown", trapFocus);
trigger.focus(); // Return focus to the element that opened the modal
}
function getFocusableElements() {
return modal.querySelectorAll(
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
}
function trapFocus(event) {
if (event.key === "Escape") {
closeModal();
return;
}
if (event.key !== "Tab") return;
const focusable = getFocusableElements();
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey) {
if (document.activeElement === first) {
event.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
event.preventDefault();
first.focus();
}
}
}
document.getElementById("cancel-btn").addEventListener("click", closeModal);
trigger.addEventListener("click", openModal);
</script>
Key points in the good example:
-
The
role="dialog"andaria-modal="true"attributes tell assistive technology this is a modal context. -
tabindex="-1"on the dialog container allows it to receive programmatic focus without appearing in the natural tab order. -
The
trapFocusfunction loops focus between the first and last interactive elements. - Pressing Escape or clicking Cancel closes the modal and returns focus to the trigger button.
- The event listener is added when the modal opens and removed when it closes, keeping the page behavior clean.
By combining proper ARIA semantics, deliberate focus movement, and a reliable exit mechanism, a focus trap ensures that modal interactions are usable and understandable for everyone—regardless of how they navigate.
Related terms
Help us improve this glossary term
Scan your site
Rocket Validator scans thousands of pages in seconds, detecting accessibility and HTML issues across your entire site.