Dialog captures the wrong trigger when focusable content mounts on open
- Dominant language
- TypeScript
- Stars
- 13.1k
- Forks
- 1.1k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 669
Description
## Version
`@astryxdesign/core@0.5.0`
## Problem
A controlled `Dialog` can restore focus to `` instead of its opener when its content is mounted only while `isOpen` is true.
`DialogHeader` runs a child `useEffect` that focuses its heading on mount. React runs that child effect before the parent `Dialog` effect that assigns `triggerElementRef.current = document.activeElement`. The dialog therefore captures its own heading (or another newly mounted autofocus child), not the external trigger. If the closed state removes/sanitizes that content, the captured node is detached before close restoration.
`DialogHeader` also focuses its heading when mounted under a closed Dialog because its `shouldAutoFocus` check only excludes inline mode, not `isOpen === false`.
## Minimal shape
```tsx
setOpen(true)}>Open
{open ? (
} content={...} />
) : null}
```
1. Focus and activate `Open`.
2. Close with Escape or `onOpenChange(false)`.
3. Focus lands on `body`, not `Open`.
This shape is useful when a closed dialog must not retain a sensitive payload in the DOM.
## Expected
The Dialog captures the external active element before descendant mount/autofocus effects, and restores that element after the controlled `true -> false` transition. `DialogHeader` should not focus while the parent Dialog is closed.
Potential directions: capture the rising-edge trigger before passive descendant effects, expose `isOpen` in `DialogContext`, and make the header title the Dialog's supported `data-autofocus` target rather than running an unconditional mount effect.
Contributor guide
Research direction
Start with the controlled Dialog and DialogHeader focus and effect behavior described in the issue, then reproduce the minimal conditional-mounting shape with the Escape close path. Verify that the external opener is captured before descendant autofocus effects, restored after the true-to-false transition, and that a closed DialogHeader does not focus; the issue's potential directions identify the relevant context and autofocus areas to investigate.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- accessibility, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100