Skip to content

Move focus to a defined place when a window is closed from the keyboard - #1375

Open
captbaritone wants to merge 4 commits into
masterfrom
a11y-focus-on-close
Open

captbaritone wants to merge 4 commits into
masterfrom
a11y-focus-on-close

Conversation

@captbaritone

@captbaritone captbaritone commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Note: this now targets master and includes #1374's commit (window labels), which it builds on. Merge #1374 first, or merge this to land both.

When a keyboard user closes a window, the focused Close button is removed from the DOM and the browser drops focus to <body>. The user loses their place, and pressing Tab starts over from the top of the page.

Change

A new useRestoreFocusOnRemoval(webampNode) hook, used by App, watches for this and moves focus to the first of:

  1. the window that contained the focused element, if it's still open (e.g. a title button remounted when toggling shade mode)
  2. the main window
  3. any other open window
  4. the #webamp root node, when every window is closed (including when Webamp itself is closed from the main window's Close button)

Windows are found by a new data-window-id attribute on FocusTarget. The root node gets tabIndex = -1, and #webamp:focus joins the existing outline: 0 rule.

When it applies:

  • Keyboard only. Moving focus into a window selects it and lights up its title bar, so pointer users would see a change. It tracks the most recent keydown vs pointerdown.
  • Only when focus was really lost. The previously focused element must have been removed, and document.activeElement must now be <body>.
  • Never steals focus back. If the user had already moved focus elsewhere in the page (focusing something outside, or clicking outside), it does nothing. An embedder's onClose handler can therefore move focus to its own control, e.g. a "Reopen" button, and that wins.
  • Works in Firefox. Like FocusTarget, it uses a MutationObserver, because Firefox doesn't fire blur/focusout when a focused element is removed.

onClose still fires as before.

Testing

  • New useRestoreFocusOnRemoval.test.tsx covers:
    • closing a window → main window
    • a removed button inside a still-open window → that window
    • closing the main window → another window
    • closing everything → root
    • pointer users → unchanged
    • focus already outside, or a click outside → unchanged
  • tsc, eslint, prettier --check and vitest run pass locally.

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Size Change: +605 B (+0.12%)

Total Size: 491 kB

📦 View Changed
Filename Size Change
packages/webamp/built/webamp.bundle.min.js 298 kB +304 B (+0.1%)
packages/webamp/built/webamp.lazy-bundle.min.js 193 kB +301 B (+0.16%)

compressed-size-action

@captbaritone
captbaritone changed the base branch from a11y-window-labels to master October 15, 2026 16:29
@captbaritone
captbaritone force-pushed the a11y-focus-on-close branch 3 times, most recently from f3376e1 to f731955 Compare October 15, 2026 16:59
FocusTarget wraps the contents of every window, so give it role="group" and
an accessible name: "Main window", "Equalizer", "Playlist" or "Milkdrop".
This tells screen reader users which window a control belongs to, and lets
tests scope role-based queries to a window (several windows have a "Close"
button).
Closing a window removes the focused element, and the browser drops focus to
<body>. For keyboard users, move focus instead to the window that contained it
(if still open), else the main window, else any open window, else the Webamp
root node (now tabIndex=-1, with its focus outline suppressed like everything
else in Webamp).

Pointer users are unaffected: moving focus selects the window it lands in,
which would visibly change its title bar. Focus is also left alone if the user
had already moved it elsewhere in the page, so embedders can move focus in
their onClose handler.
@netlify

netlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for tourmaline-kringle-c98715 ready!

Name Link
🔨 Latest commit 66891bd
🔍 Latest deploy log https://app.netlify.com/projects/tourmaline-kringle-c98715/deploys/6ad10b2ad69a68af4418fb8b
😎 Deploy Preview https://deploy-preview-1375--tourmaline-kringle-c98715.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant