Skip to content

fix(Dialog): restore focus to trigger after overlay click#2655

Merged
zernonia merged 1 commit into
v2from
fix/dialog-overlay-focus-return
May 22, 2026
Merged

fix(Dialog): restore focus to trigger after overlay click#2655
zernonia merged 1 commit into
v2from
fix/dialog-overlay-focus-return

Conversation

@zernonia

Copy link
Copy Markdown
Member

🔗 Linked issue

closes #2649

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

When the dialog is closed via the overlay click, focus falls to <body> instead of returning to the trigger. Escape and the close button work fine; only the overlay click is affected.

Root cause: the sequence is pointerdowndismissDialogContent unmounts → FocusScope restores focus to the trigger. The browser then dispatches the compatibility mousedown event, but the overlay is already gone, so mousedown fires on <body>. The browser's default mousedown behavior on a non-focusable element blurs the just-focused trigger — focus falls to body.

Fix: add @pointerdown.left.prevent to DialogOverlayImpl. Per the Pointer Events spec, preventing default on pointerdown suppresses the corresponding compatibility mousedown, so no blur happens. The .left modifier scopes the prevention to primary button so right-click context menus still work.

🧪 Verification

Reproduced the bug in headless Chromium against docs/components/dialog:

pointerdown DIV.bg-blackA9         active=INPUT#name
focusout <- INPUT relatedTarget=BUTTON   (trigger gets focus)
focusin -> BUTTON Edit profile
mousedown BODY                            ← compat mousedown on body
focusout <- BUTTON relatedTarget=null     ← blurs trigger
final: BODY

After the fix, mousedown is suppressed and focus stays on the trigger.

Manually verified in the browser:

  • ✅ Overlay click: focus returns to trigger
  • ✅ Escape: focus returns to trigger (unchanged)
  • ✅ Close button: focus returns to trigger (unchanged)
  • ✅ Right-click on overlay: dialog stays open, no regression
  • ✅ Click inside dialog (inputs, buttons): focus works normally

Added a regression test for the overlay-click path; full suite (1,635 tests) passes.

🤖 Generated with Claude Code

Clicking the overlay fires `pointerdown`, which dismisses the dialog and
triggers focus restoration to the trigger. The browser then dispatches
the compatibility `mousedown` event, but the overlay is already unmounted
so `mousedown` fires on `<body>` and blurs the just-focused trigger —
focus falls to body.

Prevent the default on `pointerdown` (primary button only) so the browser
skips the compatibility `mousedown`. Right-click still works because the
`.left` modifier scopes the prevention to button 0.

Fixes #2649

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7989e48d-31e1-47f5-9e90-dc2d9d81677d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dialog-overlay-focus-return

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@zernonia zernonia changed the base branch from main to v2 May 22, 2026 07:43
@zernonia zernonia merged commit e17b2fe into v2 May 22, 2026
3 checks passed
@TriNextAndre

Copy link
Copy Markdown

Hi @zernonia, I think your fix broke the usage with DialogScrollContent. Could you please check that?

<Dialog :open="open">
           <DialogScrollContent>
                <DialogHeader>
                ...
                </DialogHeader>
                ...
                <DialogFooter>
                ...
                </DialogFooter>
           </DialogScrollContent>
</Dialog>

zernonia added a commit that referenced this pull request May 29, 2026
#2655 added `@pointerdown.left.prevent` on the overlay so focus stays on
the trigger after the dialog closes. When `DialogContent` is nested inside
`DialogOverlay` (a common centering pattern), that handler also fired for
pointerdown events bubbling up from controls inside the content, calling
`preventDefault()` on them. That suppressed native behavior — `<select>`
dropdowns wouldn't open and `<input>`/`<textarea>` wouldn't focus on click.

Add the `.self` modifier so the prevention only applies when the pointer
lands directly on the overlay backdrop, preserving the #2655 behavior
while leaving descendant interactions untouched.

Fixes #2660

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zernonia added a commit that referenced this pull request May 29, 2026
…lf (#2668)

#2655 added `@pointerdown.left.prevent` on the overlay so focus stays on
the trigger after the dialog closes. When `DialogContent` is nested inside
`DialogOverlay` (a common centering pattern), that handler also fired for
pointerdown events bubbling up from controls inside the content, calling
`preventDefault()` on them. That suppressed native behavior — `<select>`
dropdowns wouldn't open and `<input>`/`<textarea>` wouldn't focus on click.

Add the `.self` modifier so the prevention only applies when the pointer
lands directly on the overlay backdrop, preserving the #2655 behavior
while leaving descendant interactions untouched.

Fixes #2660

Co-authored-by: Claude Opus 4.8 (1M context) <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.

[Bug]: Inconsistent focus return when closing dialog

2 participants