# Keyboard traps in editors and embedded widgets

Source: https://easeweb.dev/learn/keyboard-traps
Topics: Keyboard and focus > Focus never gets stuck (WCAG 2.1.2)
The fix: Either stop intercepting Tab, so the browser moves focus as usual, or keep Tab for indenting and let Escape release the next Tab, with visible text that says so. Check third-party widgets the same way, because the trap is often in code you did not write.
Test with: keyboard, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html https://www.w3.org/WAI/WCAG22/Techniques/general/G21 https://codemirror.net/examples/tab/

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A form has a Notes editor followed by a Save note button. Tab into the editor and type. Press Tab to move on to Save note: the editor indents the line instead. Press Shift + Tab to go back: it indents again. Nothing on the keyboard moves focus out of the editor, so Save note and the rest of the page can't be reached.

## Why it fails

To support indenting, a script listens for keydown on the editor, cancels every Tab with `preventDefault()` and inserts spaces. It never checks Shift, and it offers no other key to leave. Success criterion 2.1.2 allows a component to keep focus only when the user is told how to move it away. Here there is no way out and nothing to tell.

## Who is affected

Anyone who uses a keyboard instead of a pointer: people with motor disabilities, screen reader users, switch and voice users whose software sends Tab, and power users. A sighted mouse user can click elsewhere and never notice. A keyboard user has to reload the page and loses what they typed.

## What automation and AI miss

The markup is a labelled `textarea`, so a static scan finds nothing wrong. The trap lives in a script that only acts on a key press, and a probe has to press Tab from inside the component to see it. AI coding tools often add Tab-to-indent because developers expect it in code editors, without the way out. Traps also hide in third-party widgets, such as consent banners, chat windows and embedded maps, whose code nobody on the team reads.

## Before: an editor that keeps every Tab

```html
<label for="notes">Notes</label>
<textarea id="notes" name="notes"></textarea>
<button type="submit">Save note</button>
```

```js
const editor = document.getElementById('notes');
editor.addEventListener('keydown', (event) => {
  if (event.key !== 'Tab') return;
  event.preventDefault();
  editor.setRangeText('  ', editor.selectionStart, editor.selectionEnd, 'end');
});
```

Every Tab, with or without Shift, is cancelled and turned into two spaces. Focus can't leave the editor with the keyboard, and the page says nothing about another key.

## After: Tab moves focus, as the browser intends

```html
<label for="notes">Notes</label>
<textarea id="notes" name="notes"></textarea>
<button type="submit">Save note</button>
```

```js
// No keydown handler: Tab and Shift + Tab move focus as usual.
```

Remove the handler. A `textarea` already lets people type, and the browser moves focus out of it on Tab and Shift + Tab. This is the smallest change, and nobody has to learn a special key. If people need indentation, add an Indent button or indent automatically when they press Enter.

## After: Tab indents, and Escape releases the next Tab

```html
<label for="notes">Notes</label>
<p id="notes-keys">Tab indents. To leave the editor, press Escape, then Tab.</p>
<textarea id="notes" name="notes" aria-describedby="notes-keys"></textarea>
<button type="submit">Save note</button>
```

```js
const editor = document.getElementById('notes');
let release = false;
editor.addEventListener('keydown', (event) => {
  if (event.key === 'Escape') {
    release = true;
    return;
  }
  if (event.key === 'Tab' && !release) {
    event.preventDefault();
    editor.setRangeText('  ', editor.selectionStart, editor.selectionEnd, 'end');
  }
  if (event.key !== 'Shift') release = false;
});
```

Tab still indents, but after Escape the next Tab or Shift + Tab is left to the browser and focus moves on. Any other key cancels the release. The instruction is visible text and is linked to the editor with `aria-describedby`, so a screen reader reads it when focus enters. This is the pattern code editors such as CodeMirror use, and it meets 2.1.2 because the user is told how to leave.

## Which option to use

Both pass. Letting Tab move focus costs nothing and asks nothing of the user, so use it for notes, comments, messages and any field where indenting isn't central to the task. Keep Tab for indenting only in editors where people really write code or structured text, and then always pair it with the release key and its written instruction.

## Implementation decisions

Escape often has another job, such as closing the dialog the editor sits in. If so, decide which wins, or pick a different release key and write that one down; some editors use Ctrl + M to switch Tab between indenting and moving focus. Never lock focus with a `blur` or `focusout` handler that calls `focus()` again, for example to force a field to be valid before leaving: that is a trap too. A modal dialog is the one place where keeping focus inside is correct; the `modal-focus-management` guide covers it. For third-party widgets, including ones in shadow DOM or an `iframe`, test the live page and report traps to the vendor; if you can't fix it, load the widget only after the user opts in, or replace it.

## Verify the fix

1. Tab into the component, use it, then press Tab: focus must reach the next control.
2. Go back into it and press Shift + Tab: focus must reach the previous control.
3. Where a release key exists, follow only the text the page shows and confirm it gets you out.
4. Tab through the whole page, including third-party widgets such as consent banners, chat and embedded frames, and confirm you can pass each one in both directions.
5. With a screen reader, enter the component and confirm the way out is announced.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the editor or any third-party script changes.

## Limits

This is a starting pattern, not a guarantee for every application. The snippet indents with two spaces and leaves out undo and outdenting. Rich-text and code editor libraries have their own Tab settings; check what yours does, then test the complete journey with a keyboard and your target assistive technology.
