Repository navigation
fix(BottomSheet): focus returned after a pointer close gets no focus ring - #7388
Conversation
…ring When its exit motion ends, the standalone BottomSheet closes its native dialog, which restores focus to the opener, then calls a plain focus() on finalFocusRef or the opener. Nothing consults the last input, so WebKit rings the returned focus after a tap and Chromium carries a keyboard ring over to it after a mouse close. A DropdownMenu bottom-sheet trigger the caller renders shows it, since only built-in triggers hide it with a style. The sheet now turns on the shared modality tracking and returns focus with focusForLastInput: after pointer input it asks for focusVisible: false, clearing a ring the native close already drew; after keyboard input it focuses as before. The helper is Dialog's, moved into utils/focusReturn.ts with the same hunks as meta#7382, so the two merge in either order. Tests: four unit cases (three fail on main), a cross-browser spec on a new DropdownMenu story (custom trigger, bottom-sheet presentation).
|
AI review status for this pull request.
|
There was a problem hiding this comment.
This fix looks correct and complete. A bottom sheet now returns focus with a focus ring only after keyboard input, as the interaction-modality rule (INV1) asks, and the keyboard path and focus destination are unchanged. The new unit cases fail without the fix and pass with it, and the new browser spec passes in Chromium and WebKit: the returned trigger no longer matches :focus-visible after a pointer pick, and an Escape close still keeps the ring. Nothing else is needed from you.
[Automated review]
PR Analysis Report
No new or modified components detected. Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Visual Regression44 of 448 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 24 more. Generated by PR Enrichment workflow | View full report |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
cixzhang
left a comment
There was a problem hiding this comment.
CodeBunny approved at this head; CI green. Approved.
User impact
After a pointer close, the control a BottomSheet returns focus to can show the focus ring, as if on a keyboard: in iOS Safari after a tap pick from a
DropdownMenubottom-sheet presentation whose trigger the caller renders (renderTrigger), and in Chromium when a keyboard-opened sheet is closed with the mouse.Problem and solution fit
focus()onfinalFocusRefor the opener. Nothing consults the last input: WebKit rings the restored focus after a tap, Chromium carries the ring over from a keyboard-focused row, and focusing the already-focused element changes nothing.Expected behavior and authority
architecture:interaction-modalityINV1 (current): programmatic focus after pointer input shows no keyboard indicator; after keyboard input it does.Smallest restoration
BottomSheethost turns on the shared modality tracking and returns focus withfocusForLastInput(target). After pointer input it asks forfocusVisible: false, first clearing a ring the native close already drew; after keyboard input it focuses as before.utils/focusReturn.tswith an optionalFocusOptionsargument. Those hunks match fix(DropdownMenu): a trigger the caller renders gets no focus ring after a pointer pick #7382's exactly, so they merge in either order.Evidence
mainbuild plus the story fails its keyboard-open, pointer-pick case in Chromium (:focus-visibletrue). iOS 26.2 Simulator, real Mobile Safari, same story, a tap on the trigger then on "Edit project": onmainthe trigger is focused,:focus-visible,outline: solid 2px(2 of 2, screenshots match).:focus-visible,outline: none(2 of 2).main. No pull-request workflow runs the browser spec; release checks run its Chromium half.Scope
Testing