+
diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts
index a02d957afec..2e36db2cfa1 100644
--- a/packages/ui/src/hooks/index.ts
+++ b/packages/ui/src/hooks/index.ts
@@ -10,6 +10,7 @@ export * from "./use-in-viewport";
export * from "./use-input-focused";
export * from "./use-intersection-observer";
export * from "./use-keyboard-shortcut";
+export * from "./use-latest-callback";
export * from "./use-local-storage";
export * from "./use-media-query";
export * from "./use-optimistic-update";
diff --git a/packages/ui/src/hooks/use-keyboard-shortcut.tsx b/packages/ui/src/hooks/use-keyboard-shortcut.tsx
index a87f4ef34a7..ce7477bce7e 100644
--- a/packages/ui/src/hooks/use-keyboard-shortcut.tsx
+++ b/packages/ui/src/hooks/use-keyboard-shortcut.tsx
@@ -57,6 +57,10 @@ export function useKeyboardShortcut(
(e: KeyboardEvent) => {
if (options.enabled === false) return;
+ // Skip if another handler already consumed this key (e.g. Radix
+ // popovers/menus/dialogs close on Escape in the capture phase).
+ if (e.defaultPrevented) return;
+
const target = e.target as HTMLElement;
const existingModalBackdrop = document.getElementById("modal-backdrop");
const existingSheetBackdrop = document.querySelector(
diff --git a/packages/ui/src/hooks/use-latest-callback.ts b/packages/ui/src/hooks/use-latest-callback.ts
new file mode 100644
index 00000000000..573864f6696
--- /dev/null
+++ b/packages/ui/src/hooks/use-latest-callback.ts
@@ -0,0 +1,24 @@
+import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
+
+const useIsomorphicLayoutEffect =
+ typeof window !== "undefined" ? useLayoutEffect : useEffect;
+
+/**
+ * Returns a stable function that always invokes the latest `callback`,
+ * so it can be passed to memoized children (or used in effects) without
+ * their identity changing when the callback is recreated by the caller.
+ */
+export function useLatestCallback
any>(
+ callback: T | undefined,
+) {
+ const callbackRef = useRef(callback);
+
+ useIsomorphicLayoutEffect(() => {
+ callbackRef.current = callback;
+ });
+
+ return useCallback(
+ (...args: Parameters) => callbackRef.current?.(...args) as ReturnType,
+ [],
+ );
+}