fix(react-dialog): respect autoFocus element over first focusable on dialog open#36038
fix(react-dialog): respect autoFocus element over first focusable on dialog open#36038
autoFocus element over first focusable on dialog open#36038Conversation
#35749) Agent-Logs-Url: https://github.com/microsoft/fluentui/sessions/32dab34c-d281-4bef-af30-8c263a2a57d6 Co-authored-by: tudorpopams <97875118+tudorpopams@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/fluentui/sessions/32dab34c-d281-4bef-af30-8c263a2a57d6 Co-authored-by: tudorpopams <97875118+tudorpopams@users.noreply.github.com>
autoFocus element over first focusable on dialog open
|
@copilot fix CI |
Agent-Logs-Url: https://github.com/microsoft/fluentui/sessions/c48f37af-fb63-4e6c-8a5e-f1f3c5437885 Co-authored-by: tudorpopams <97875118+tudorpopams@users.noreply.github.com>
PR Review: #36038 — fix(react-dialog): respect
|
| Category | Status | Notes |
|---|---|---|
| Change file | PASS | @fluentui-react-dialog-890df3cd-...json, type patch. Description correctly cites #35749 (the real issue — avoiding a prior Copilot session's wrong-issue-reference mistake). |
| V9 patterns | PASS | Hook is an internal utility; no React.FC, no styles touched, existing useFluent_unstable() targetDocument usage preserved for the dev-warning branch. |
| Dep layers | PASS | No package.json or new-import changes. |
| SSR safety | PASS | querySelector is called on dialogRef.current (a ref to a real DOM node that only exists after mount); no global document/window access. The existing no-focusable-element fallback path and its dev warning are unchanged. |
| Testing | PASS | New useFocusFirstElement.test.ts covers both paths — the no-autofocus case asserts findFirstFocusable is called and the first button is focused; the autofocus case asserts both that the autofocus input is focused and that findFirstFocusable is NOT called and the first button is NOT focused. The negative assertions are the ones that matter — they're what actually proves the preference order. |
| API surface | PASS | Internal hook; no public API change. etc/react-dialog.api.md untouched (correctly). |
| Accessibility | PASS | Makes React's autoFocus={true} behave per its documented contract inside dialogs. The existing empty-dialog fallback (dialogRef.current?.focus() with the dev warning for #25150) is preserved. |
| Security/Quality | PASS | No eval/dangerouslySetInnerHTML/console.log/@ts-ignore/any. |
Recommendation
APPROVE
Clean minimal fix. The reshape from a single assignment to two lines preserves the existing "no focusable element at all" warning path exactly, and the test file explicitly asserts the wrong element is NOT focused in the autofocus case — which is the assertion that proves the fix, not just the positive path. Drawer coverage is handled automatically via DialogSurface composition, avoiding a duplicate fix on the drawer side.
Posted via the /review-pr skill.
📊 Bundle size reportUnchanged fixtures
|
|
Pull request demo site: URL |
| @@ -0,0 +1,7 @@ | |||
| { | |||
There was a problem hiding this comment.
🕵🏾♀️ visual changes to review in the Visual Change Report
vr-tests-react-components/Charts-DonutChart 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Charts-DonutChart.Dynamic - Dark Mode.default.chromium.png | 7530 | Changed |
| vr-tests-react-components/Charts-DonutChart.Dynamic.default.chromium.png | 5581 | Changed |
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default - RTL.submenus open.chromium.png | 404 | Changed |
| vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png | 413 | Changed |
vr-tests-react-components/Positioning 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png | 852 | Changed |
| vr-tests-react-components/Positioning.Positioning end.chromium.png | 778 | Changed |
vr-tests-react-components/ProgressBar converged 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png | 32 | Changed |
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png | 26 | Changed |
vr-tests-react-components/TagPicker 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/TagPicker.disabled.chromium.png | 677 | Changed |
There were 1 duplicate changes discarded. Check the build logs for more information.

When a Dialog/Drawer contained an element with
autoFocus={true}preceded by any other focusable element, the autofocus was silently ignored —findFirstFocusablealways won by DOM order.Changes
useFocusFirstElement.ts: Before callingfindFirstFocusable, query the dialog root for[autofocus]. React lowersautoFocus={true}to the DOMautofocusattribute on mount, soquerySelector('[autofocus]')reliably finds it. Falls back tofindFirstFocusable(and the existing dev warning) when none is present.useFocusFirstElement.test.ts(new): Unit tests covering the autofocus-wins path and the no-autofocus fallback path, with mockeduseFocusFindersanduseFluent_unstable.Since
OverlayDrawerusesDialogSurfaceinternally, this fix covers both Dialog and Drawer without any changes to drawer code.