PLAINFINCH learn by building

Keyboard traps

You will find a script that keeps keyboard focus stuck in a notes box and change it so Tab moves on again.

What is a keyboard trap?

A keyboard trap is a place on a page where focus goes in and cannot come out again with the keyboard. Someone presses Tab and nothing moves. They press Shift+Tab and nothing moves. They might press Escape, then try the arrow keys, and focus still sits on the same control. For a mouse user the page looks normal, because they can click somewhere else. For someone who only uses a keyboard, the rest of the page has gone.

WCAG 2.2 covers this in 2.1.2 No Keyboard Trap, a level A success criterion. It says that if focus can move into a part of the page with the keyboard, it must be able to move out with the keyboard too. When leaving needs anything other than the usual keys, Tab, Shift+Tab, arrow keys or Escape, the page has to tell people what to press. The criterion is one of the few that WCAG marks as non-interference: a trap anywhere on the page fails the whole page, even a part that has nothing to do with what the visitor came for.

A trap stops people completely. A keyboard user who gets stuck in a comment box can’t reach the submit button or the menu, and may not know the browser shortcut that jumps to the address bar, and a screen reader user may not know why every key press seems to do nothing.

Who gets stuck, and what does it feel like?

Many people use a keyboard on its own. Some have a tremor or limited hand movement and find a mouse hard to aim. Some are blind and use a screen reader, which is driven from the keyboard. Some use a switch, a single large button that steps through the page one stop at a time, and the switch software sends Tab and Enter on their behalf. Some use voice control and say “press Tab”. Power users of all kinds move through forms with Tab because it is faster.

For all of them a trap feels like the computer has frozen. A switch user may press their switch dozens of times before deciding the page is broken. A screen reader user hears the same field announced over and over, or hears nothing at all. People with cognitive disabilities may assume they did something wrong. Most will leave, and many will not come back.

Look at the starting page. A mechanic’s note form has three controls. Tab works from the ticket number into the notes box, then stops dead. The Send note button is unreachable, so nobody who relies on the keyboard can send a note.

How does a script trap focus?

Browsers move focus on Tab by themselves. A trap happens when a script steps in. The most common way is a keydown listener that checks for Tab and calls event.preventDefault(). That call cancels the browser’s normal action, which for Tab is moving focus to the next stop. In the demo the script does it so people can type a tab character into their list of parts. The intention is kind, and the result locks people in.

The second common way is a listener on blur or focusout that puts focus straight back: “stay in the phone box until the number looks complete”. The third is a widget, such as a map, a slideshow or a code editor, that listens to every key so it can use arrows or letters, and cancels all of them, Tab included. The fourth is content inside an <iframe> from another site, like a chat window or a video player, whose own script grabs keys once focus enters it.

<script>
  // This is the trap: Tab never reaches the browser
  notes.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      e.preventDefault();
      // ...insert a tab character instead
    }
  });
</script>
FigureOn the repair note form, Tab reaches the notes textarea at stop 2 but never moves on to Send note, because a keydown listener calls e.preventDefault() when the key is Tab.On the repair note form, Tab reaches the notes textarea at stop 2 but never moves on to Send note, because a keydown listener calls e.preventDefault() when the key is Tab.
One preventDefault() on Tab cancels the browser's own focus move, so focus stays in the notes box. Delete the script and Tab reaches Send note again.

How do you fix the trap in the demo?

Remove the code that cancels Tab. In the demo that means deleting the whole <script>. A <textarea> already accepts line breaks and plenty of text, and a list of parts reads fine with dashes or one item per line. Nobody needs a real tab character in a note to a mechanic.

Press Check my work, then open the **Keyboard** view. The Tab stops are the ticket number, the notes box and the Send note button, and pressing Tab or Shift+Tab from the notes box now moves focus in both directions. Try it with a real keyboard in the Page view too: click into the notes, press Tab, and watch the focus ring land on the button.

When a control really does need Tab for itself, as a code editor might, it must give people a documented way out. Some editors switch Tab between “indent” and “move focus” with a keyboard shortcut and say so on screen next to the editor. That meets 2.1.2 because the method is explained. It still adds effort, so keep it for tools that need indenting and keep plain Tab behaviour everywhere else.

When is holding focus inside something correct?

A modal dialog is the main exception. While a modal is open, the page behind it should not be usable, so focus should stay inside the dialog. That is containment with an exit. Escape closes it, a visible close button closes it, and focus goes back to the control that opened it. The native <dialog> element opened with showModal() does this for you: the rest of the page becomes inert, and Escape closes the dialog without any script.

The difference between a modal and a trap is the way out. If a pop-up keeps focus inside but has no keyboard-reachable close button and ignores Escape, it is a trap. The Break It page has exactly this kind of panel: a newsletter box that sits over the page with a close × that keyboard users can’t reach. The dialogs lesson builds a complete modal later in the course.

Watch for one more case: a panel with position: fixed that covers the bottom of the screen. Focus can move behind it to links that are hidden from view. Focus can still leave, so this is a different failure: 2.4.11 Focus Not Obscured (Minimum), since people can’t see what has focus.

What should Escape do?

Escape is the key people press when something has appeared that they didn’t want. Users expect it to close menus, pop-ups, tooltips, autocomplete lists and dialogs, and to put focus back where they were. It should never submit a form, delete text the person typed or leave the page.

A small listener is enough for a custom panel. Listen on the document, check for Escape, hide the panel and move focus to a sensible place, usually the control that opened it. Don’t use Escape to undo unrelated work, and don’t assume every visitor will think to press it: a visible close button is still needed for people who use a mouse, a touch screen or voice control.

<script>
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      panel.hidden = true;
      opener.focus();
    }
  });
</script>

How do you test a page for traps?

Put the mouse out of reach and Tab from the address bar to the end of the page, then Shift+Tab all the way back. Every stop should be reachable in both directions. At each widget, try the keys it is likely to use: arrows in a map or slider, Escape in anything that pops up, Enter and Space on buttons. If any stop refuses to let go, you have found a trap.

Do this with the page in different states. Open each menu and pop-up, type into each field and leave some empty, because many traps only appear when a field has a certain value. The postcode box on the Break It page lets go only after five characters are typed.

The **Keyboard** view helps. Focus a control, press Tab with the buttons and watch whether the highlighted stop changes. Automatic checkers, including the Checks view, rarely find traps, because a trap is behaviour and only shows up when someone presses keys. That makes the manual Tab test the one to keep.

What do AI tools get wrong here?

Asked for “a textarea that supports tabs” or “a form that validates as you go”, AI tools often produce exactly the code in the demo: a keydown listener that cancels Tab, or a blur handler that refocuses the field until the value is valid. Both read as helpful in a code review and both trap people. Search any generated script for preventDefault, key === 'Tab', keyCode === 9, .focus() inside blur or focusout, and listeners on document that cancel every key.

Generated pop-ups are the other weak spot. They often use a <div> or <span> for the close control, with no Escape handling and no focus moved in or out. Ask for a native <dialog> and a <button> instead, then test with the keyboard before trusting it.

Field notes

  • A keyboard trap lets focus go into a spot but never back out.
  • People who use only a keyboard cannot reach the rest of the page past a trap.
  • Most traps come from a script that cancels the Tab key.
  • A modal dialog is the main place where holding focus is correct.

Easy to get wrong

Cancelling Tab stops the browser moving focus, so keyboard users are stuck in that control. Validation can wait for submit, and focus should always be free to move.

Refocusing on blur forces people to fix one field before they can do anything else, including reading the instructions further down. It is a trap even when the intent is to help.

A <span> can’t receive focus, so keyboard users can’t reach the only way out of the panel. A <button> is focusable and works with Enter and Space.

Holding focus in a modal is correct only when there is a keyboard way out. Without it, the modal is a trap.

Questions people ask

Is it a trap if Escape gets people out?

No, as long as Escape really works and people can find out about it. WCAG allows a way out other than Tab if the page tells people what it is. Escape is a widely known convention for closing things, so a modal that closes on Escape and has a visible close button is fine.

Can an embedded video or map trap focus?

Yes. Content from another site inside an <iframe> runs its own scripts, and some capture keys. Tab through the embed before you ship. If it holds focus, look for a setting that turns off keyboard capture, or choose a different embed.

Does the Checks view find keyboard traps?

Rarely. A trap is behaviour, so a checker has to press keys in the right place with the right content to see it. The check on this page does that for one field. On your own pages, the manual Tab and Shift+Tab test is the one to trust.

What if I really need Tab to indent text?

Make indenting the exception people switch on, show the shortcut that switches it next to the field, and keep plain Tab moving focus by default. Most forms never need it.

Keep going