diff --git a/src/components/Modal/Modal.test.tsx b/src/components/Modal/Modal.test.tsx
index 7a423235e..c673eda58 100644
--- a/src/components/Modal/Modal.test.tsx
+++ b/src/components/Modal/Modal.test.tsx
@@ -4,6 +4,7 @@ import React, { FC, useEffect, useState } from "react";
import Button from "components/Button";
import Input from "components/Input";
+import ContextualMenu from "components/ContextualMenu";
import Modal from "./Modal";
describe("Modal ", () => {
@@ -223,6 +224,41 @@ describe("Modal ", () => {
expect(input).toHaveValue("delete item1");
});
+ it("should allow Escape to close a ContextualMenu inside the modal before closing the modal", async () => {
+ const handleCloseModal = jest.fn();
+ const handleMenuToggle = jest.fn();
+
+ render(
+
+
+ ,
+ );
+
+ // The contextual menu dropdown should be open (aria-hidden="false")
+ const dropdown = document.querySelector(
+ '.p-contextual-menu__dropdown[aria-hidden="false"]',
+ );
+ expect(dropdown).toBeInTheDocument();
+
+ // Press Escape — should close the menu, not the modal
+ await userEvent.keyboard("{Escape}");
+
+ // The modal close handler should NOT have been called
+ expect(handleCloseModal).not.toHaveBeenCalled();
+
+ // The dropdown should now be closed (no longer present with aria-hidden="false")
+ expect(
+ document.querySelector(
+ '.p-contextual-menu__dropdown[aria-hidden="false"]',
+ ),
+ ).not.toBeInTheDocument();
+ });
+
it("updates focusable elements when an initially disabled button becomes enabled", async () => {
const user = userEvent.setup();
diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx
index 0294a101e..7ca08f566 100644
--- a/src/components/Modal/Modal.tsx
+++ b/src/components/Modal/Modal.tsx
@@ -91,6 +91,19 @@ export const Modal = ({
const handleEscKey = (
event: KeyboardEvent | React.KeyboardEvent,
) => {
+ // If there is an open contextual menu dropdown inside (or alongside) this
+ // modal, let the Escape event propagate so that the menu's own keydown
+ // handler can close it first. The modal should only intercept Escape when
+ // no child overlay is open. This fixes the bug where a ContextualMenu
+ // rendered inside a Modal cannot be closed with the Escape key because
+ // stopImmediatePropagation() was called unconditionally, preventing the
+ // menu's document-level keydown listener from ever firing.
+ const hasOpenDropdown = !!document.querySelector(
+ '.p-contextual-menu__dropdown[aria-hidden="false"]',
+ );
+ if (hasOpenDropdown) {
+ return;
+ }
if ("nativeEvent" in event && event.nativeEvent.stopImmediatePropagation) {
event.nativeEvent.stopImmediatePropagation();
} else if ("stopImmediatePropagation" in event) {