diff --git a/.changeset/tidy-layers-restore.md b/.changeset/tidy-layers-restore.md
new file mode 100644
index 0000000000..115fc8b93e
--- /dev/null
+++ b/.changeset/tidy-layers-restore.md
@@ -0,0 +1,5 @@
+---
+'@radix-ui/react-dismissable-layer': patch
+---
+
+Fix body pointer-events cleanup when outside pointer events are disabled.
diff --git a/packages/react/dismissable-layer/src/dismissable-layer.test.tsx b/packages/react/dismissable-layer/src/dismissable-layer.test.tsx
new file mode 100644
index 0000000000..005f238fde
--- /dev/null
+++ b/packages/react/dismissable-layer/src/dismissable-layer.test.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { cleanup, render } from '@testing-library/react';
+import { afterEach, describe, it, expect } from 'vitest';
+import { DismissableLayer } from './dismissable-layer';
+
+describe('DismissableLayer', () => {
+ afterEach(() => {
+ cleanup();
+ document.body.style.pointerEvents = '';
+ });
+
+ it('restores body pointer events after a disabled layer is removed from the stack', () => {
+ document.body.style.pointerEvents = 'auto';
+
+ const { rerender } = render(
+ ,
+ );
+
+ expect(document.body.style.pointerEvents).toBe('none');
+
+ rerender();
+ expect(document.body.style.pointerEvents).toBe('none');
+
+ rerender();
+ expect(document.body.style.pointerEvents).toBe('auto');
+ });
+});
+
+function TestLayers({
+ firstLayerDisabled,
+ secondLayerOpen,
+}: {
+ firstLayerDisabled: boolean;
+ secondLayerOpen: boolean;
+}) {
+ return (
+ <>
+
+ {secondLayerOpen ? : null}
+ >
+ );
+}
diff --git a/packages/react/dismissable-layer/src/dismissable-layer.tsx b/packages/react/dismissable-layer/src/dismissable-layer.tsx
index 9869f91992..96aad14f50 100644
--- a/packages/react/dismissable-layer/src/dismissable-layer.tsx
+++ b/packages/react/dismissable-layer/src/dismissable-layer.tsx
@@ -22,6 +22,15 @@ const DismissableLayerContext = React.createContext({
branches: new Set(),
});
+function restoreBodyPointerEvents(
+ ownerDocument: Document,
+ context: React.ContextType,
+) {
+ if (context.layersWithOutsidePointerEventsDisabled.size === 0) {
+ ownerDocument.body.style.pointerEvents = originalBodyPointerEvents;
+ }
+}
+
type DismissableLayerElement = React.ComponentRef;
type PrimitiveDivProps = React.ComponentPropsWithoutRef;
interface DismissableLayerProps extends PrimitiveDivProps {
@@ -121,11 +130,10 @@ const DismissableLayer = React.forwardRef {
- if (
- disableOutsidePointerEvents &&
- context.layersWithOutsidePointerEventsDisabled.size === 1
- ) {
- ownerDocument.body.style.pointerEvents = originalBodyPointerEvents;
+ if (disableOutsidePointerEvents) {
+ context.layersWithOutsidePointerEventsDisabled.delete(node);
+ restoreBodyPointerEvents(ownerDocument, context);
+ dispatchUpdate();
}
};
}, [node, ownerDocument, disableOutsidePointerEvents, context]);
@@ -141,9 +149,10 @@ const DismissableLayer = React.forwardRef {
const handleUpdate = () => force({});