Skip to main content
Accessibility

Focus Trap

  • keyboard navigation
  • focus management
  • modal dialog
  • WCAG
  • interactive components

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" and aria-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 trapFocus function 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.

Help us improve this glossary term

Was this guide helpful?

Scan your site

Rocket Validator scans thousands of pages in seconds, detecting accessibility and HTML issues across your entire site.

🌍 Trusted by teams worldwide

Validate at scale.
Ship accessible websites, faster.

Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.

Scheduled Reports
API Access
Open Source Standards
$7 / 7 days

Pro Trial

Full Pro access. Cancel anytime.

Start Pro Trial →

Join teams across 40+ countries