Custom dialogs and third-party embeds trap focus when they intercept Tab without releasing it on Escape or close
2.1.2 No Keyboard Trap
In Plain Language
2.1.2 No Keyboard Trap (Level A) says that if keyboard focus can enter a component, focus must be movable away from that component using only the keyboard[1]. If exit requires a non-standard key (anything beyond Tab, Shift+Tab, or arrow keys), the page must tell the user which key to press before or at the moment they enter the component.
2.1.2 is distinct from 2.1.1 Keyboard: 2.1.1 asks whether a control is reachable by keyboard at all; 2.1.2 asks whether, once focus lands there, the user can leave.
Why It Matters
- A keyboard trap is a terminal failure for keyboard-only users. Once focus is stuck, no other control on the page is reachable, browser-level shortcuts aside -- the only recovery is closing the tab.
- Screen-reader users, switch users, and sip-and-puff users navigate the entire page through the keyboard interface. A trap removes every downstream control from the session.
- The failure almost never comes from semantic HTML. Native form controls, links, and buttons do not trap focus. Traps come from custom widgets that call
event.preventDefault()on Tab without moving focus, from third-party iframes (chat overlays, video players, CAPTCHA widgets) whose internal focus logic does not bubble Escape or Tab back to the parent document, and from custom modal dialogs that implement a focus trap on entry but forget to release it on Escape or on close. - Sighted keyboard users hit this too -- power users on Tab-only workflows and users with motor impairments who cannot use a pointing device.
Examples
<dialog> with close button and Escape handler
✔ Focus is trapped inside the modal, but Escape or the close button always releases it
<dialog id="confirm-dialog">
<h2>Confirm action</h2>
<p>Are you sure you want to delete this item?</p>
<button autofocus>Cancel</button>
<button>Delete</button>
</dialog>
<!-- Native <dialog> supports Escape to close by default.
Focus trap keeps Tab cycling inside the modal,
but the user can always press Escape to leave. -->
<div class='modal'> -- no close button, no Escape handler
✘ Focus enters the overlay but there is no keyboard method to leave -- the user is trapped
<!-- FAILS: custom modal traps focus with no exit -->
<div class="overlay" tabindex="0"
onfocus="this.querySelector('input').focus()">
<input type="text" placeholder="Search..." />
<!-- No close button, no Escape key handler -->
<!-- Tab cycles between the div and the input forever -->
</div>
<div role='application'> with on-screen instructions
Press Escape to exit the editor
✔ Non-standard exit key (Escape) is clearly communicated to the user
<div role="application" aria-label="Code editor">
<p class="keyboard-hint">
Press Escape to return to the main page.
</p>
<textarea id="editor"></textarea>
</div>
<script>
editor.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
// Move focus out of the editor
document.getElementById('next-section').focus();
}
});
</script>
<iframe src='third-party-widget.html'>
✘ Third-party iframe captures Tab key -- keyboard user cannot leave the widget area
How to Fix It
- Prefer the native
<dialog>element withshowModal(). The platform implementation handles the focus trap, restores focus to the element that calledshowModal()on close, and closes on Escape without extra script. This eliminates the most common custom-modal failure mode. - For custom dialogs, implement the WAI-ARIA APG dialog pattern. The pattern specifies role, focus entry, focus trap semantics, and, critically, focus release on Escape and on close with focus returned to the trigger[2]. A focus trap without a release path is the exact shape of a 2.1.2 failure.
- Never swallow Tab in a handler without moving focus. Calling
event.preventDefault()onkeydownfor Tab or Shift+Tab and not programmatically advancing focus is what creates a trap. If a composite widget (grid, listbox, tree) uses the roving-tabindex pattern, the outer Tab must still move focus out of the widget. - Audit third-party embeds. Chat overlays, video players, CAPTCHA widgets, rich-text editors, and embedded map controls run inside iframes or inject script that can intercept Tab. Load each embed, Tab into it, and confirm Tab and Shift+Tab eventually return focus to the host document. If an embed traps focus, replace it or wrap it in a host-page escape hatch.
- Document any non-standard exit key at the moment of entry. 2.1.2 permits non-standard exit mechanics only if the user is told about them in advance. A visible hint ("Press Escape to leave the editor") rendered near the component, or an accessible description on the container, satisfies the advisory requirement.
- Verify with keyboard-only testing. Unplug the mouse. Tab through the page start-to-finish. Every interactive component must be exitable by Tab, Shift+Tab, or an exit key the page has disclosed. If the only way out is closing the tab, it is a trap.
References
- [1] W3C (2023). Understanding Success Criterion 2.1.2: No Keyboard Trap. W3C, Accessed 2026-04-07. https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html ↩
- [2] W3C (2024). WAI-ARIA Authoring Practices Guide. W3C, Accessed 2026-04-07. https://www.w3.org/WAI/ARIA/apg/ ↩