diff --git a/packages/react/src/dialog/root/DialogRoot.test.tsx b/packages/react/src/dialog/root/DialogRoot.test.tsx
index 08ff285fd0..e960fcc5d6 100644
--- a/packages/react/src/dialog/root/DialogRoot.test.tsx
+++ b/packages/react/src/dialog/root/DialogRoot.test.tsx
@@ -337,6 +337,36 @@ describe('', () => {
});
expect(handleOpenChange.callCount).to.equal(1);
});
+
+ it('dismisses when clicking a descendant element within the user backdrop', async () => {
+ const handleOpenChange = spy();
+
+ const { queryByRole } = await render(
+
+
+
+ Child
+
+
+
+ ,
+ );
+
+ const backdropChild = screen.getByTestId('backdrop-child');
+
+ fireEvent.mouseDown(backdropChild);
+
+ expect(queryByRole('dialog')).not.to.equal(null);
+ expect(handleOpenChange.callCount).to.equal(0);
+
+ fireEvent.click(backdropChild);
+
+ await waitFor(() => {
+ expect(queryByRole('dialog')).to.equal(null);
+ });
+ expect(handleOpenChange.callCount).to.equal(1);
+ expect(handleOpenChange.firstCall.args[1].reason).to.equal('outside-press');
+ });
});
it('waits for the exit transition to finish before unmounting', async ({ skip }) => {
diff --git a/packages/react/src/dialog/root/useDialogRoot.ts b/packages/react/src/dialog/root/useDialogRoot.ts
index 383872680a..51c266cfdf 100644
--- a/packages/react/src/dialog/root/useDialogRoot.ts
+++ b/packages/react/src/dialog/root/useDialogRoot.ts
@@ -12,7 +12,7 @@ import {
useInteractions,
useRole,
} from '../../floating-ui-react';
-import { getTarget } from '../../floating-ui-react/utils';
+import { contains, getTarget } from '../../floating-ui-react/utils';
import { useScrollLock } from '../../utils/useScrollLock';
import { useTransitionStatus, type TransitionStatus } from '../../utils/useTransitionStatus';
import type { RequiredExcept, HTMLProps, FloatingUIOpenChangeDetails } from '../../utils/types';
@@ -134,14 +134,15 @@ export function useDialogRoot(params: useDialogRoot.Parameters): useDialogRoot.R
}
const target = getTarget(event) as Element | null;
if (isTopmost && dismissible) {
- const eventTarget = target as Element | null;
// Only close if the click occurred on the dialog's owning backdrop.
// This supports multiple modal dialogs that aren't nested in the React tree:
// https://github.com/mui/base-ui/issues/1320
if (modal) {
- return internalBackdropRef.current || backdropRef.current
- ? internalBackdropRef.current === eventTarget || backdropRef.current === eventTarget
- : true;
+ const internalBackdrop = internalBackdropRef.current;
+ const userBackdrop = backdropRef.current;
+ if (internalBackdrop || userBackdrop) {
+ return contains(internalBackdrop, target) || contains(userBackdrop, target);
+ }
}
return true;
}