Managing Focus in Popups and Dialogs

Manage keyboard focus in extension popups, side panels and in-page dialogs: initial focus when the popup opens, the native dialog element, focus trapping, restoring focus after close, and focus inside shadow DOM.

Published October 2, 2026 Updated October 2, 2026 8 min read
Table of Contents

A keyboard user presses the extension’s shortcut, the popup opens — and nothing is focused, so the first Tab press moves focus somewhere unexpected, and a screen reader announces “document” with no hint of what to do. They open a confirmation dialog inside the popup, press Tab three times, and focus escapes to the content behind it. They close the dialog and focus jumps back to the top. Focus management is the difference between UI that works with a keyboard and UI that technically contains focusable elements. Extension surfaces have their own quirks: the popup opens from the toolbar or a shortcut, injected dialogs live inside someone else’s page, and shadow DOM changes how focus is reported. This guide covers each surface. It belongs to internationalization and accessibility.

Where focus goes in each extension surface

When the popup opens, the browser moves focus into the popup document, but which element receives it is up to you — without autofocus or a script, nothing inside is focused and the first Tab picks the first focusable element. Side panels do not take focus when they open; the user’s focus stays on the page until they move it. Options pages and extension tabs behave like normal web pages. UI injected by a content script is in the page’s document, competing with the page’s own focus handling. In every case, the rules are the same as the web’s: put focus somewhere sensible on open, keep it inside modal UI while it is open, and return it to where it came from when the UI closes.

Focus through a confirmation dialogThe user presses Delete in the popup list; the popup opens a native modal dialog which focuses the Cancel button; Tab cycles only within the dialog; on Escape or Cancel the dialog closes and focus returns to the Delete button that opened it.UserPopup listDialogactivate Delete on item 3showModal() → focus CancelTab, Tab → stays in dialogEscapeclose → focus Delete on item 3
Open → contain → restore.

Step-by-step: deliberate focus

1. Focus the primary control when the popup opens

1<!-- popup.html -->
2<input id="search" type="search" aria-label="Search saved pages" autofocus>
1// popup.js — when the target depends on state
2const target = state.items.length ? document.querySelector("#search") : document.querySelector("#get-started");
3requestAnimationFrame(() => target.focus());

Execution context: the popup page. autofocus is enough for a fixed target; a script lets the first-run popup focus “Get started” and the regular popup focus search. Focusing after the first frame avoids a race with the popup’s sizing. Pick the control most users want first — usually search, the main action, or the first item — not the close button.

2. Use the native dialog element for modals

1<dialog id="confirm-delete" aria-labelledby="cd-title">
2  <h2 id="cd-title">Delete this page?</h2>
3  <p>This removes it from every synced device.</p>
4  <form method="dialog">
5    <button value="cancel" autofocus>Cancel</button>
6    <button value="delete" class="danger">Delete</button>
7  </form>
8</dialog>
 1const dialog = document.querySelector("#confirm-delete");
 2let opener;
 3function confirmDelete(button) {
 4  opener = button;
 5  dialog.showModal();
 6}
 7dialog.addEventListener("close", () => {
 8  if (dialog.returnValue === "delete") deleteItem(opener.dataset.id);
 9  opener?.focus();
10});

Execution context: the popup, options page or side panel. showModal() makes the rest of the page inert, traps Tab inside the dialog, closes on Escape, and exposes the right roles to assistive technology — no focus-trap library needed. autofocus on Cancel makes the safe choice the default for a destructive action. The close handler restores focus to the button that opened it.

Focus behaviour by surfacePopup, side panel, options page and in-page injected dialogs compared on whether they receive focus on open, how to trap focus for modals, and where to return focus.SurfaceFocus on openModal trappingReturn focus toPopupDocument; set elementdialog.showModal()Opener buttonSide panelNo (page keeps it)dialog.showModal()Opener buttonOptions pageNormal pagedialog.showModal()Opener buttonInjected dialogMust focus explicitlyshowModal in shadow rootPage's previous activeEle…
Same three rules, different starting points.

3. Restore focus after removing content

1async function removeItem(li) {
2  const next = li.nextElementSibling ?? li.previousElementSibling;
3  li.remove();
4  (next?.querySelector("button") ?? document.querySelector("#search")).focus();
5  announce(chrome.i18n.getMessage("itemRemoved"));
6}

Execution context: the popup. When the focused element is removed, focus falls to <body> and keyboard users lose their place. Moving focus to the neighbouring item keeps them in the list. Pair it with a polite live-region announcement, as in announcing dynamic updates to screen readers.

4. Move focus into the side panel on explicit request

 1// sidepanel.js
 2chrome.runtime.onMessage.addListener((msg) => {
 3  if (msg.type === "focus-panel") document.querySelector("#notes").focus();
 4});
 5// service worker: after a command opens the panel
 6chrome.commands.onCommand.addListener(async (cmd, tab) => {
 7  if (cmd !== "open-notes") return;
 8  await chrome.sidePanel.open({ tabId: tab.id });
 9  chrome.runtime.sendMessage({ type: "focus-panel" }).catch(() => {});
10});

Execution context: the side panel page and service worker. A side panel opened by a keyboard shortcut should put focus where the user can type; one opened as a passive companion should not steal focus from the page. Let the trigger decide: commands move focus, background updates do not.

Should opening this UI move focus?Decision tree: UI opened by a deliberate user action such as a click or shortcut moves focus into it; UI that appears on its own such as a toast or a passively updating panel does not take focus; modal UI always takes focus and traps it.How did the UI appear?user click or shortcutMove focus inprimary controlappeared on its ownLeave focusannounce insteadmodalFocus + trapshowModal()
Move focus when the user asked for the UI, never when it appeared on its own.

5. Handle focus for dialogs injected into pages

 1// content script
 2function openInPageDialog(contentNode) {
 3  const previous = document.activeElement;
 4  const host = document.createElement("div");
 5  const root = host.attachShadow({ mode: "closed" });
 6  const dialog = document.createElement("dialog");
 7  dialog.setAttribute("aria-label", "Save to Readable");
 8  dialog.append(contentNode);
 9  root.append(styleSheet(), dialog);
10  document.documentElement.append(host);
11  dialog.showModal();
12  dialog.addEventListener("close", () => { host.remove(); previous?.focus?.(); }, { once: true });
13}

Execution context: a content script. showModal() works inside a shadow root and places the dialog in the top layer, above the page’s own stacking contexts, while making the page inert. Remember the page’s previously focused element and return focus to it when the dialog closes, so the user resumes where they were in the page. See showing a modal dialog over any page.

6. Account for shadow DOM when reading focus

1function deepActiveElement(root = document) {
2  let el = root.activeElement;
3  while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement;
4  return el;
5}

Execution context: extension pages or content scripts using web components. document.activeElement reports the shadow host, not the focused element inside it. Walk down through open shadow roots to find the real one; for closed roots, keep a reference yourself.

7. Keep a logical tab order

Never use positive tabindex values; arrange the DOM in reading order and let Tab follow it. Use tabindex="-1" for elements you focus programmatically (headings after navigation, error summaries) but which should not be Tab stops. For composite widgets like a list of results, consider a roving tabindex so Tab enters and leaves the list in one step while arrow keys move within it.

8. Test with only a keyboard

Unplug the mouse — figuratively — and complete each task: open the popup with its shortcut, search, act on an item, open and dismiss a dialog, open the side panel. At each step, know where focus is. Repeat with a screen reader as described in testing extension UI with a screen reader.

Common mistakes

  • No initial focus in the popup. The first Tab lands somewhere arbitrary.
  • Custom modal divs. Focus escapes; use <dialog> with showModal().
  • Not restoring focus on close. Users lose their place.
  • Stealing focus for passive updates. Announce instead.
  • Positive tabindex. Breaks natural order.

Cross-browser variation

  • Chrome / Edge: popup receives focus on open; <dialog> and top layer fully supported, including in shadow roots.
  • Firefox: same behaviour; the sidebar does not take focus on open.
  • Safari: popovers receive focus on open; <dialog> is supported in current versions. Keyboard navigation of links requires the user to enable “Press Tab to highlight each item” in Safari settings.

Verification

  1. Open the popup by shortcut and confirm focus is on the intended control.
  2. Open a dialog, press Tab repeatedly and confirm focus stays inside; press Escape and confirm focus returns to the opener.
  3. Delete an item and confirm focus moves to its neighbour.
  4. Open an in-page dialog and confirm focus returns to the page element afterwards.

FAQ

Should the popup close button get initial focus?

No. Focus the main control; Escape closes the popup anyway.

Do I need a focus-trap library?

Not with <dialog> and showModal(). Libraries are for non-modal patterns that need custom containment.

Why does focus go to the page after my side panel opens?

Side panels do not take focus by default. Move it explicitly when the user opened the panel by command.

Can a content script focus page elements?

Yes, it shares the page’s DOM, but only do so in direct response to the user, and restore what was focused before.

Other UI/UX Patterns & Interactive Components Resources