Lesson 7 of 30
Opening a dialog doesn't just show new content on top of the page — it makes a promise about where keyboard focus goes, stays, and returns to. Most home-rolled implementations keep two-thirds of that promise and quietly drop the last part.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
Inside an open dialog's focus trap, the LAST focusable element has focus and the user presses Tab (not Shift+Tab). What should happen?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
When a dialog, modal, or any overlay opens on top of page content, keyboard focus needs active management — the browser won't do the right thing on its own. Three things have to happen, in order, across the dialog's full lifecycle:
Tab press lands on whatever was next in that background page's tab order — content that's now visually hidden behind an overlay the user can't even see.Tab and Shift+Tab should cycle only through the dialog's own focusable elements — never escape into the page behind it. This is a focus trap: the boundary elements wrap around instead of letting focus leave. Content behind the overlay is supposed to be inert for as long as the dialog is open, and a focus trap is what makes that actually true for keyboard users, not just visually true for sighted mouse users.<body>, not to the top of the page. This is the detail most home-rolled implementations miss. A keyboard user who opened a dialog from a specific row in a table, or a specific item in a list, expects to resume exactly there when they close it — not lose their place and have to re-navigate the whole page to find it again.let previouslyFocused;
function openDialog(dialog, trigger) {
previouslyFocused = trigger; // remember who opened it
dialog.hidden = false;
const first = dialog.querySelector("[tabindex], button, input, a[href]");
first?.focus(); // move focus IN
dialog.addEventListener("keydown", trapTabKey);
}
function closeDialog(dialog) {
dialog.hidden = true;
dialog.removeEventListener("keydown", trapTabKey);
previouslyFocused?.focus(); // restore focus to the trigger, not <body>
}
function trapTabKey(e) {
if (e.key !== "Tab") return;
const focusable = [...dialog.querySelectorAll("[tabindex], button, input, a[href]")];
const [first, last] = [focusable[0], focusable[focusable.length - 1]];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus(); // wrap backward
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus(); // wrap forward
}
}
Each of the three lifecycle moments has exactly one line doing the real work: first?.focus() on open, the wrap-around logic inside trapTabKey for the duration, and previouslyFocused?.focus() on close. Drop any one of them and the dialog still looks correct to a mouse user — the bug only shows up for someone navigating by keyboard alone.
<body> or just leaving it wherever the dialog's close button happened to be is a common, incomplete substitute.Shift+Tab from the first element should wrap to the last, and Tab from the last should wrap to the first — a trap that only catches one direction still lets focus leak out the other way."Walk me through what happens to keyboard focus when this modal opens and closes" is a specific, common diagnostic question, and a strong answer names all three lifecycle moments — move in, trap, restore — by name, not just "make sure focus works."