Modal focus management is the part of popup work that is easy to ship broken and hard to notice with a mouse. If keyboard focus stays on the page behind the overlay, a keyboard user tabs through invisible links while the dialog sits on top of them; a screen reader user may never hear the dialog was opened at all. This guide gives you three short manual tests — keyboard entry, focus containment and focus return — plus a test script you can run in minutes on any modal or popup, including a WidgetJar popup you have just published.
You do not need a screen reader to run the baseline checks. A keyboard and a browser are enough. What you need is a clear definition of passed and failed, so a change to content or trigger settings does not quietly undo the behavior.
What focus containment and focus return actually mean
The WAI-ARIA Authoring Practices describe the expected behavior precisely: in a modal dialog, focus moves to an element inside the dialog when it opens, Tab and Shift+Tab stay inside the dialog, and when the dialog closes, focus returns to the element that invoked it. As the WAI-ARIA modal dialog pattern states it, Tab moves to the next tabbable element and wraps from the last to the first, while content outside the dialog is inert.
That gives you three testable promises:
- Keyboard entry: opening the dialog moves focus into it.
- Focus containment: tabbing cycles within the dialog and never reaches the page behind it.
- Focus return: closing the dialog puts focus back on the trigger, or on another logical element if the trigger no longer exists.
The same source also recommends a visible control with a button role that closes the dialog — a close icon or cancel button — inside the tab sequence. Escape is expected to close modal dialogs, so test that path too.
Start from the native dialog element when you can
Before writing a trap for focus, check whether you can inherit one. The browser's built-in <dialog> element opened with showModal() renders the rest of the page inert and moves focus to the first focusable element inside. MDN's HTML dialog element reference explains that modal dialogs opened this way are implicitly aria-modal="true" and can be closed with Escape by default, and that the autofocus attribute is the way to choose a better initial target than "first focusable thing."
Two warnings from that same reference are worth pinning above your desk. Do not put tabindex on the dialog element itself — it is not interactive — and if you build a custom dialog from divs instead, you must replicate the default behaviors, including inert background content. Teams building an embed on a CMS often skip this step; the same culture of verifying what an embed actually does on the page is covered in How to Embed a Widget Without Touching Your Backend.
If your popup is not a native dialog — a marketing overlay injected by a third-party script, for example — assume nothing and run all three tests below.
Test 1 — Keyboard entry: does focus land inside?
- Reload the page so focus starts at the top.
- Press Tab repeatedly until the trigger (button or link) has a visible focus ring.
- Activate it with Enter or Space — do not click.
- Look at the focus ring. Is it on an element inside the dialog?
- If you have a screen reader available, listen for the dialog's accessible name being announced.
Pass: focus is on a sensible control inside the dialog — often the close button, or the first field if the dialog's job is data entry. Fail: focus stays on the trigger, jumps to the top of the document, or vanishes entirely (no ring anywhere).
The "sensible" part matters. Announcing a long block of text as one string is hard to follow; if your popup contains headings or multiple paragraphs, the ARIA guidance suggests focusing a static element at the start of the content instead of the first input. Record which element you chose in your test notes so a later content edit does not silently move initial focus.
Test 2 — Focus containment: does Tab escape?
- Open the dialog by keyboard.
- Press Tab until you reach the last control. Press Tab once more.
- The focus ring should wrap to the first control inside the dialog.
- Press Shift+Tab from the first control. It should wrap to the last.
- Repeat for roughly two full cycles.
- Then try to reach the page behind: Tab past the loop, and also click on the dimmed area or scroll the background with arrow keys.
Pass: every Tab stop is a visible, interactive element inside the dialog, and the background cannot be reached. Fail: the ring disappears behind the overlay while you keep tabbing, or the browser scrolls the page behind the dialog.
A frequent subtle failure: the dialog looks contained because the backdrop covers the page, but focus is actually walking through background links. The backdrop is a visual device, not an interaction boundary. Only inert background content or a real focus trap stops it.
Test 3 — Focus return: does closing put you back?
- Open the dialog by keyboard from a known trigger.
- Close it with Escape. Where is the focus ring now?
- Reopen it, and close it with the close button or a cancel button instead. Where is the ring now?
- If the dialog can be closed by submitting a form from inside, test that route as well.
Pass: focus is back on the trigger element in all cases. Fail: focus is at the top of the document, on body, or lost — the next Tab press starts the user at the beginning of the page rather than where they were. For a popup that appears near the bottom of a long page, that failure feels like being thrown back to the top of the site.
The ARIA pattern allows one exception: if the invoking element no longer exists, focus should move to another element that fits the workflow. Test that too if your dialog's action removes the trigger from the page.
Manual test script and a checklist to copy
Use this as your sign-off sheet for any popup change:
- Open by keyboard only (Tab to trigger, Enter/Space): focus enters dialog, on the planned element.
- First Tab stop is visible; focus ring contrast is clear against the dialog background.
- Tab through all controls and past the last: wraps to the first. Shift+Tab wraps back.
- Background links, buttons and form fields are unreachable by Tab while open.
- Escape closes the dialog and focus returns to the trigger.
- The close button is in the tab sequence and also returns focus to the trigger.
- A dialog containing a form still returns focus correctly after submit or cancel.
- Tab order matches reading order; no
tabindexvalues greater than zero. - Result recorded with browser, page URL, trigger selector and initial-focus element.
Record pass or fail per line, plus the test date and browser version. That short record is what turns "it felt fine last time" into something you can compare after a redesign. Accessibility checks like these are part of a wider baseline; our WCAG requirements checklist covers the surrounding criteria you will meet after fixing focus.
Common failure modes and the smallest fix
Focus stays on the trigger. The open handler shows the dialog but never moves focus. Fix: move focus to a planned element inside the dialog at open time — for a native dialog, set autofocus on the right control.
Background tabs are reachable. The overlay is visual only. Fix: use a native modal dialog or render the rest of the page inert, and test with Tab, not with a mouse.
No visible focus ring. A CSS reset removed outlines. Fix: restore a clear focus indicator for every interactive element, including the close button on a dark backdrop.
Focus is lost on close. Nothing restores it, or the trigger is re-rendered after the dialog closes so the stored reference is stale. Fix: store the invoking element before opening and refocus it after closing; if it is gone, choose the next logical stop.
Escape does nothing. A custom overlay listener was never added, or it was added to the wrong node. Test Escape in every way the dialog can open, including auto-open on page load.
Running the same checks on a WidgetJar popup
WidgetJar is an embeddable widget platform: you create and configure widgets in the panel, then install the WidgetJar embed script on your registered website domain. If you use the popup widget, treat it as any other overlay in your test suite rather than assuming it behaves one way or another. Open the live page, not a preview, run the three tests above in a fresh browser session, and note which element receives focus first.
Then do the regression pass that matters most: after you change the popup's content or its trigger settings in the panel, publish and repeat the keyboard-entry and focus-return checks. A new first field, a new button order, or a changed trigger element can alter where focus lands and what focus returns to. If your popup collects data through an existing form, our guide to making an AI-generated HTML form work without a backend covers the submission side, and you should still close the loop by testing focus return after that form is submitted. The same method applies whether you added the popup by hand or via the no-code route in adding a popup to a static HTML site.
Ready to test your own? Build the popup from the WidgetJar popup widget page, install it on your registered domain, then run the checklist once with a keyboard only. If a step fails, fix it before you turn the popup on for everyone — three short tests now save a much longer accessibility review later.