Nested Radix Dialog dismissal unexpectedly closes parent Sheet
Nested Dialog dismissal unexpectedly closes parent Sheet
Description
When a Dialog is opened from inside a Sheet, dismissing the nested Dialog can also cause the parent Sheet to close unexpectedly.
This appears to happen when the child Dialog is closed as part of a pointer interaction. After the Dialog unmounts, the remaining interaction appears to be interpreted by the parent Sheet as an outside interaction.
This is particularly problematic when the Sheet contains unsaved form data, because closing the parent Sheet causes the entire form to be lost.
Reproduction
A simplified component structure is:
<Sheet>
<SheetContent>
{/* Parent form */}
<input placeholder="Enter some data" />
<Dialog>
<DialogTrigger asChild>
<button>Open Dialog</button>
</DialogTrigger>
<DialogContent>
{/* Child dialog */}
<button onClick={handleSave}>
Save
</button>
</DialogContent>
</Dialog>
</SheetContent>
</Sheet>Steps to reproduce
- Open the
Sheet. - Enter some data into the form inside the Sheet.
- Open the nested
Dialog. - Perform an action inside the Dialog that saves/submits the child data.
- Close the Dialog as part of that action.
- Observe that the parent Sheet also closes.
Expected behavior
Closing the nested Dialog should only close the Dialog.
The parent Sheet should remain open and preserve its current state/form data.
Actual behavior
The nested Dialog closes, but the parent Sheet may also receive an outside-interaction event and close.
This can result in all data entered into the parent Sheet being lost.
Additional observation
The issue appears to be related to the timing of the child Dialog unmounting during the pointer interaction.
The child Dialog is rendered in a portal, so its DOM is outside the Sheet's DOM subtree. When the Dialog closes during the interaction, the remaining portion of that interaction appears to reach the parent Sheet and is treated as an outside interaction.
As a diagnostic/workaround, preventing the parent's onInteractOutside event while the child Dialog is open, and for a short period immediately after it closes, prevents the issue.
For example:
const SETTLE_MS = 250;
const closedAtRef = useRef(0);
const wasOpenRef = useRef(isChildOpen);
useEffect(() => {
if (wasOpenRef.current && !isChildOpen) {
closedAtRef.current = Date.now();
}
wasOpenRef.current = isChildOpen;
}, [isChildOpen]);
const handleInteractOutside = useCallback(
(event: { preventDefault: () => void }) => {
if (
isChildOpen ||
Date.now() - closedAtRef.current < SETTLE_MS
) {
event.preventDefault();
}
},
[isChildOpen],
);With this guard in place, the parent Sheet remains open as expected.
This suggests that the problem may involve an interaction/event-ordering edge case when a portaled nested Dialog is dismissed.
Questions
Is this expected behavior when nesting a Dialog inside a Sheet, or is there an interaction propagation/unmount timing issue that Radix should handle?
If this is a known limitation, is there a recommended way to prevent the parent Sheet from receiving the trailing interaction when the child Dialog closes?
Environment
- OS: Ubuntu 22.04
- Browser: Firefox 154.0
- React: 19.1.1
- React DOM: 19.1.1
- @radix-ui/react-dialog: 1.1.15
- @radix-ui/react-alert-dialog: 1.1.15
- @radix-ui/react-popover: 1.1.15
- Vite: 7.1.2
- TypeScript: 5.8.3
Additional context
The issue is reproducible with a nested overlay structure where the child Dialog is opened from within the parent Sheet.
The most important requirement is that dismissing the child Dialog must not dismiss the parent Sheet or discard its state.
Source: radix-ui/themes