diff --git a/frontend/src/app/main/ui/ds/tooltip/tooltip.cljs b/frontend/src/app/main/ui/ds/tooltip/tooltip.cljs
index 4751d81dcf..05246f7f23 100644
--- a/frontend/src/app/main/ui/ds/tooltip/tooltip.cljs
+++ b/frontend/src/app/main/ui/ds/tooltip/tooltip.cljs
@@ -160,7 +160,7 @@
tooltip-ref (mf/use-ref nil)
- container (hooks/use-portal-container)
+ container (hooks/use-portal-container :tooltip)
id
(d/nilv id internal-id)
diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs
index 42560cd8fe..ae8ebd30d5 100644
--- a/frontend/src/app/main/ui/hooks.cljs
+++ b/frontend/src/app/main/ui/hooks.cljs
@@ -380,17 +380,35 @@
state))
+(defn- get-or-create-portal-container
+ "Returns the singleton container div for the given category, creating
+ and appending it to document.body on first access."
+ [category]
+ (let [body (dom/get-body)
+ id (str "portal-container-" category)]
+ (or (dom/query body (str "#" id))
+ (let [container (dom/create-element "div")]
+ (dom/set-attribute! container "id" id)
+ (dom/append-child! body container)
+ container))))
+
(defn use-portal-container
- "Creates a dedicated div container for React portals. The container
- is appended to document.body on mount and removed on cleanup, preventing
- removeChild race conditions when multiple portals target the same body."
- []
- (let [container (mf/use-memo #(dom/create-element "div"))]
- (mf/with-effect []
- (let [body (dom/get-body)]
- (dom/append-child! body container)
- #(dom/remove-child! body container)))
- container))
+ "Returns a shared singleton container div for React portals, identified
+ by a logical category. Available categories:
+
+ :modal — modal dialogs
+ :popup — popups, dropdowns, context menus
+ :tooltip — tooltips
+ :default — general portal use (default)
+
+ All portals in the same category share one
on document.body,
+ keeping the DOM clean and avoiding removeChild race conditions."
+ ([]
+ (use-portal-container :default))
+ ([category]
+ (let [category (name category)]
+ (mf/with-memo [category]
+ (get-or-create-portal-container category)))))
(defn use-dynamic-grid-item-width
([] (use-dynamic-grid-item-width nil))
diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs
index 5df1cc3daa..6e9b1df7d4 100644
--- a/frontend/src/app/main/ui/modal.cljs
+++ b/frontend/src/app/main/ui/modal.cljs
@@ -84,7 +84,7 @@
(mf/defc modal-container*
{::mf/props :obj}
[]
- (let [container (hooks/use-portal-container)]
+ (let [container (hooks/use-portal-container :modal)]
(when-let [modal (mf/deref ref:modal)]
(mf/portal
(mf/html [:> modal-wrapper* {:data modal :key (dm/str (:id modal))}])
diff --git a/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs
index ab0dc6326d..c870baf9fb 100644
--- a/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs
@@ -517,7 +517,7 @@
dropdown-direction-change* (mf/use-ref 0)
top (+ (get-in mdata [:position :y]) 5)
left (+ (get-in mdata [:position :x]) 5)
- container (hooks/use-portal-container)]
+ container (hooks/use-portal-container :popup)]
(mf/use-effect
(mf/deps is-open?)
diff --git a/frontend/src/app/main/ui/workspace/tokens/management/node_context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/management/node_context_menu.cljs
index d37e628d02..f150240cf1 100644
--- a/frontend/src/app/main/ui/workspace/tokens/management/node_context_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/tokens/management/node_context_menu.cljs
@@ -36,7 +36,7 @@
dropdown-direction-change* (mf/use-ref 0)
top (+ (get-in mdata [:position :y]) 5)
left (+ (get-in mdata [:position :x]) 5)
- container (hooks/use-portal-container)
+ container (hooks/use-portal-container :popup)
delete-node (mf/use-fn
(mf/deps mdata)
diff --git a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs
index a8687c9719..d688588e2f 100644
--- a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs
+++ b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs
@@ -114,7 +114,7 @@
:is-open? true
:rect rect))))))
- container (hooks/use-portal-container)]
+ container (hooks/use-portal-container :popup)]
[:div {:on-click on-open-dropdown
:disabled (not can-edit?)