No Keyboard Trap
If focus can be moved to a component using the keyboard, it can be moved away again using only the keyboard. If a non-standard key is needed to exit, the user must be told.
Who this affects
Keyboard-only users. A trap is catastrophic rather than merely inconvenient — the only escape is to close the tab and lose all work.
The failure and the fix
Each example below is a self-contained page, loaded in a frame so its markup cannot affect this page. Open either one on its own to test it with a keyboard or screen reader.
Fails 2.1.2
Open the failing example in a new tab
fail.html — the problem
<h1>Newsletter</h1>
<p><a href="#before">A link before the dialog</a></p>
<!-- FAILURE: this focus trap has no exit. It cycles focus inside the dialog
forever: Escape is not handled, there is no close button, and the trap
runs even though the dialog is meant to be dismissible.
A trap is the most severe keyboard failure there is. The user's only
remaining option is to close the browser tab and lose their work. -->
<div class="modal" id="dlg">
<h2>Before you go…</h2>
<div class="field">
<label for="em">Email address</label>
<input type="email" id="em">
</div>
<button type="button" class="button" id="sub">Subscribe</button>
</div>
<p><a href="#after">A link after the dialog</a></p>
<p id="before">Before.</p><p id="after">After.</p>
Meets 2.1.2
Open the passing example in a new tab
pass.html — the fix
<h1>Newsletter</h1>
<p><a href="#before">A link before the dialog</a></p>
<!-- FIX: a modal SHOULD hold focus while it is open - that part is correct and
expected. What makes it lawful rather than a trap is that there is always
a way out using only the keyboard:
- Escape closes it
- a visible Close button is in the cycle
- focus returns to the element that opened it
If a component genuinely needs an unusual key to exit, 2.1.2 requires you
to tell the user what that key is, at the point they need to know. -->
<div class="modal" id="dlg" role="dialog" aria-modal="true" aria-labelledby="dlg-h">
<h2 id="dlg-h" tabindex="-1">Before you go…</h2>
<p id="dlg-hint">Press <kbd>Escape</kbd> to close this dialog.</p>
<div class="field">
<label for="em">Email address</label>
<input type="email" id="em" autocomplete="email">
</div>
<button type="button" class="button" id="sub">Subscribe</button>
<button type="button" class="button button--secondary" id="close">Close</button>
</div>
<p><a href="#after">A link after the dialog</a></p>
<p id="before">Before.</p><p id="after">After.</p>
How to test it
Tab into every widget — modals, custom media players, embedded editors, third-party iframes — and confirm you can Tab or Escape back out. Test in both directions with Tab and Shift+Tab.
-
Automated
Detecting a trap requires actually driving focus through the page and observing that it cannot leave.
No axe rule maps to this criterion. It has to be checked by a person.
-
Keyboard
Unplug the mouse. Move through the page with Tab, Shift+Tab, Enter, Space and the arrow keys. See the keyboard testing script.
-
Screen reader
Listen to both examples with NVDA, JAWS, VoiceOver or TalkBack and compare what is announced. See the screen reader cheat sheets.
-
Visual
Zoom to 200% and 400%, narrow the viewport to 320 px, and apply the text-spacing overrides. See the visual testing procedures.
Related criteria
In the specification
- Understanding 2.1.2 No Keyboard Trap — the W3C explanation, intent and exceptions
- 2.1.2 in the WCAG 2.2 Recommendation — the normative wording
WCAG 2.2 Demo Suite