Skip to content

fix(a11y): restore focus after dialog opened from switch#1584

Open
binrogithub wants to merge 1 commit into
Telefonica:masterfrom
binrogithub:fix-dialog-switch-return-focus
Open

fix(a11y): restore focus after dialog opened from switch#1584
binrogithub wants to merge 1 commit into
Telefonica:masterfrom
binrogithub:fix-dialog-switch-return-focus

Conversation

@binrogithub

Copy link
Copy Markdown

Summary

Fixes #1568 — Dialog does not return focus to the trigger element when opened from a Switch inside Row.

Root cause: Two interacting issues:

  1. Switch didn't explicitly focus itself on click before calling onChange, so document.activeElement could be wrong when the Dialog captured the trigger element.
  2. FocusTrap remained active during the dialog closing animation, causing react-focus-lock to steal focus back after triggerEl.focus() fired in the onDestroy callback.

Fix:

  • switch-component.tsx: Added e.currentTarget.focus() before handleChange() in the onClick handler, ensuring document.activeElement is the Switch when onChange fires synchronously.
  • focus-trap.tsx: Added returnFocusOnDeactivate prop that maps to react-focus-lock's returnFocus prop, making focus restoration behavior explicit and configurable.
  • dialog.tsx: Passed returnFocusOnDeactivate={false} and disabled={isClosing} to FocusTrap. This disables the focus trap when the dialog starts closing (preventing react-focus-lock from stealing focus back) and makes it explicit that DialogRoot.onDestroy is the single source of truth for focus restoration.

Test plan

  • New test: returns focus to the trigger button after alert closes (baseline)
  • New test: returns focus to the switch inside a row after confirm closes (exact bug scenario, pointer interaction)
  • New test: returns focus to the switch after alert closes (keyboard activation) (keyboard interaction)
  • All existing dialog tests pass (16/16)
  • All existing switch tests pass (7/7)
  • All existing drawer tests pass (6/6) — Drawer uses useRestoreFocus, not affected
  • All existing sheet tests pass (19/19) — Sheet uses focusTriggerElement(), not affected
  • All existing list/row tests pass (19/19)
  • All existing navigation bar tests pass (2/2)
  • TypeScript: no errors in changed files
  • ESLint: no errors in changed files
  • Pre-commit hooks (prettier) pass

🤖 Generated with Claude Code

…1568)

When a Dialog is opened from a Switch inside a Row, focus was not
returned to the Switch after the Dialog closed. Two issues:

1. Switch didn't explicitly focus itself on click before calling
   onChange, so document.activeElement could be wrong when the
   Dialog captured the trigger element.

2. FocusTrap remained active during the dialog closing animation,
   causing react-focus-lock to steal focus back after triggerEl.focus()
   fired in the onDestroy callback.

Fix by ensuring Switch focuses itself on click, disabling FocusTrap
when the dialog is closing, and making returnFocusOnDeactivate
explicitly false so DialogRoot.onDestroy is the single source of
truth for focus restoration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

[a11y] Dialog does not return focus to the trigger element (Switch inside Row)

2 participants