diff --git a/.changeset/fix-dropdown-in-dialog-dismiss-3971.md b/.changeset/fix-dropdown-in-dialog-dismiss-3971.md
new file mode 100644
index 0000000000..5ee41b3429
--- /dev/null
+++ b/.changeset/fix-dropdown-in-dialog-dismiss-3971.md
@@ -0,0 +1,8 @@
+---
+'@radix-ui/react-menu': patch
+'@radix-ui/react-dropdown-menu': patch
+'@radix-ui/react-context-menu': patch
+'@radix-ui/react-menubar': patch
+---
+
+Defer outside pointer interactions on menu content so nested dialogs are not dismissed when closing a menu.
diff --git a/apps/storybook/stories/dropdown-menu.stories.tsx b/apps/storybook/stories/dropdown-menu.stories.tsx
index f387437829..3c8a1e3bd2 100644
--- a/apps/storybook/stories/dropdown-menu.stories.tsx
+++ b/apps/storybook/stories/dropdown-menu.stories.tsx
@@ -564,6 +564,28 @@ export const NestedComposition = () => {
);
};
+export const DropdownWithinDialog = () => (
+
+
+
+
+ Dialog with dropdown
+
+ Open menu
+
+
+ Item 1
+ Item 2
+
+
+
+
+ Dialog is still open
+
+
+
+);
+
export const SingleItemAsDialogTrigger = () => {
const dropdownTriggerRef = React.useRef>(null);
const dropdownTriggerRef2 = React.useRef>(null);
diff --git a/packages/react/dismissable-layer/src/dismissable-layer.test.tsx b/packages/react/dismissable-layer/src/dismissable-layer.test.tsx
index 4ad5769f49..5903017f78 100644
--- a/packages/react/dismissable-layer/src/dismissable-layer.test.tsx
+++ b/packages/react/dismissable-layer/src/dismissable-layer.test.tsx
@@ -395,4 +395,31 @@ describe('DismissableLayer', () => {
expect(onDismiss).not.toHaveBeenCalled();
});
+
+ // Regression test for https://github.com/radix-ui/primitives/issues/3971
+ it('does not dismiss a deferred parent when a nested deferred child dismisses first', async () => {
+ const onParentDismiss = vi.fn();
+ const onChildDismiss = vi.fn();
+
+ render(
+ <>
+
+ dialog
+
+ menu
+
+
+ >,
+ );
+ await waitForDocumentPointerDownListener();
+
+ firePointerMouseClick(screen.getByTestId('parent-inside'));
+
+ await act(async () => {
+ await new Promise((resolve) => window.setTimeout(resolve, 0));
+ });
+
+ expect(onChildDismiss).toHaveBeenCalledTimes(1);
+ expect(onParentDismiss).not.toHaveBeenCalled();
+ });
});
diff --git a/packages/react/menu/src/menu.tsx b/packages/react/menu/src/menu.tsx
index 6011982e6d..e4fc3289c3 100644
--- a/packages/react/menu/src/menu.tsx
+++ b/packages/react/menu/src/menu.tsx
@@ -490,6 +490,7 @@ const MenuContentImpl = React.forwardRef