Skip to main content

Search rules

Search all UI Guides rules by name, category, or source

interactions@Ibelick

Escape Closes the Topmost Layer

MUST

MUST: Escape closes dialogs and overlays, and closes exactly ONE layer — the topmost open one. A nested popover and its parent dialog both listening for Escape on `window` means one keypress closes both and the user loses the form they were filling; the open layer handles the key and calls `stopPropagation()`. Native `<dialog>` and Radix-style primitives keep that stack for you. (Dismissal, not focus — `interactions-manage-focus` covers trap/return.)

Escape must dismiss a dialog or overlay, and it must dismiss exactly one layer — the one on top

function onKeyDown(e: React.KeyboardEvent) {
  if (e.key !== "Escape") return;
  e.stopPropagation(); // do not let the parent layer close too
  close();
}

Bad

Good

Why it matters

Escape is the universal "get me out of here" key, and a dialog that only closes via an X button strands anyone who is not holding a mouse — a keyboard user has to Tab around a trapped focus ring hunting for the exit. But the interesting half of this rule is the nested case, which almost every hand-rolled implementation gets wrong. Put a select or a popover inside a dialog, attach `keydown` for Escape on `window` in both, and one keypress closes both: the child dismisses, and the same event keeps bubbling to the dialog's listener.

The user tried to close a dropdown and lost their whole form. The fix is layer discipline — the topmost open layer handles Escape and calls `stopPropagation()` so it does not reach anything beneath it. Native `<dialog>` and Radix-style primitives maintain that stack for you, which is why the a11y skill closes with "for complex widgets (menu, dialog, combobox), prefer established accessible primitives over custom behavior".

This is distinct from interactions-manage-focus, which covers trapping focus and returning it — dismissal is a separate obligation.

Built by Gleb Stroganov, design engineer at Evil Martians.

The rules come from other people's skills and guidelines — Vercel, Rauno Freiberg, @Ibelick, impeccable, Emil Kowalski, Tailwind, RAMS — each one credited on the Sources page. The work here is extraction and wiring: every rule is pulled into one corpus, given a good and a bad example you can operate, a MUST/SHOULD/NEVER rule an agent can paste, and a link back to where it came from.