diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/group-selected-nodes-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/group-selected-nodes-chromium-linux.png
index f0ca5e712d..603c598add 100644
Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/group-selected-nodes-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/group-selected-nodes-chromium-linux.png differ
diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png
index f5c4af9bcd..7cc712f74b 100644
Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-bypassed-chromium-linux.png differ
diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-pinned-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-pinned-chromium-linux.png
index 11356a8da9..895e429a19 100644
Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-pinned-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-pinned-chromium-linux.png differ
diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unbypassed-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unbypassed-chromium-linux.png
index dfccbf6414..a37ea3f8ce 100644
Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unbypassed-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unbypassed-chromium-linux.png differ
diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unpinned-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unpinned-chromium-linux.png
index dfccbf6414..a37ea3f8ce 100644
Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unpinned-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/nodes-unpinned-chromium-linux.png differ
diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/standard-left-drag-select-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/standard-left-drag-select-chromium-linux.png
index dfccbf6414..a37ea3f8ce 100644
Binary files a/browser_tests/tests/interaction.spec.ts-snapshots/standard-left-drag-select-chromium-linux.png and b/browser_tests/tests/interaction.spec.ts-snapshots/standard-left-drag-select-chromium-linux.png differ
diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-2-nodes-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-2-nodes-chromium-linux.png
index dfccbf6414..a37ea3f8ce 100644
Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-2-nodes-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-2-nodes-chromium-linux.png differ
diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-pinned-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-pinned-chromium-linux.png
index d67b73fc3c..4512bc9da9 100644
Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-pinned-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-pinned-chromium-linux.png differ
diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-unpinned-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-unpinned-chromium-linux.png
index d6976c27e0..821129dcbc 100644
Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-unpinned-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/selected-nodes-unpinned-chromium-linux.png differ
diff --git a/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png b/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png
index c1fb924d40..32edf383c5 100644
Binary files a/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png and b/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png differ
diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue
index 309bf93421..4ad2ed7708 100644
--- a/src/components/graph/GraphCanvas.vue
+++ b/src/components/graph/GraphCanvas.vue
@@ -38,9 +38,7 @@
canvasStore.canvas to be initialized. -->
-
-
-
+
@@ -55,7 +53,6 @@ import DomWidgets from '@/components/graph/DomWidgets.vue'
import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue'
import MiniMap from '@/components/graph/MiniMap.vue'
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
-import SelectionOverlay from '@/components/graph/SelectionOverlay.vue'
import SelectionToolbox from '@/components/graph/SelectionToolbox.vue'
import TitleEditor from '@/components/graph/TitleEditor.vue'
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
diff --git a/src/components/graph/SelectionOverlay.vue b/src/components/graph/SelectionOverlay.vue
deleted file mode 100644
index 3bc41574a4..0000000000
--- a/src/components/graph/SelectionOverlay.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/graph/SelectionToolbox.vue b/src/components/graph/SelectionToolbox.vue
index bbbeadef94..2caca6bcf1 100644
--- a/src/components/graph/SelectionToolbox.vue
+++ b/src/components/graph/SelectionToolbox.vue
@@ -1,34 +1,45 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/src/composables/canvas/useSelectionToolboxPosition.ts b/src/composables/canvas/useSelectionToolboxPosition.ts
new file mode 100644
index 0000000000..8e327b05f3
--- /dev/null
+++ b/src/composables/canvas/useSelectionToolboxPosition.ts
@@ -0,0 +1,113 @@
+import { ref, watch } from 'vue'
+import type { Ref } from 'vue'
+
+import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
+import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
+import { createBounds } from '@/lib/litegraph/src/litegraph'
+import { useCanvasStore } from '@/stores/graphStore'
+
+/**
+ * Manages the position of the selection toolbox independently.
+ * Uses CSS custom properties for performant transform updates.
+ */
+export function useSelectionToolboxPosition(
+ toolboxRef: Ref
+) {
+ const canvasStore = useCanvasStore()
+ const lgCanvas = canvasStore.getCanvas()
+ const { getSelectableItems } = useSelectedLiteGraphItems()
+
+ // World position of selection center
+ const worldPosition = ref({ x: 0, y: 0 })
+
+ const visible = ref(false)
+
+ /**
+ * Update position based on selection
+ */
+ const updateSelectionBounds = () => {
+ const selectableItems = getSelectableItems()
+
+ if (!selectableItems.size) {
+ visible.value = false
+ return
+ }
+
+ visible.value = true
+ const bounds = createBounds(selectableItems)
+
+ if (!bounds) {
+ return
+ }
+
+ const [xBase, y, width] = bounds
+
+ worldPosition.value = {
+ x: xBase + width / 2,
+ y: y
+ }
+
+ updateTransform()
+ }
+
+ const updateTransform = () => {
+ if (!visible.value) return
+
+ const { scale, offset } = lgCanvas.ds
+ const canvasRect = lgCanvas.canvas.getBoundingClientRect()
+
+ const screenX =
+ (worldPosition.value.x + offset[0]) * scale + canvasRect.left
+ const screenY = (worldPosition.value.y + offset[1]) * scale + canvasRect.top
+
+ // Update CSS custom properties directly for best performance
+ if (toolboxRef.value) {
+ toolboxRef.value.style.setProperty('--tb-x', `${screenX}px`)
+ toolboxRef.value.style.setProperty('--tb-y', `${screenY}px`)
+ }
+ }
+
+ // Sync with canvas transform
+ const { startSync, stopSync } = useCanvasTransformSync(updateTransform, {
+ autoStart: false
+ })
+
+ // Watch for selection changes
+ watch(
+ () => canvasStore.getCanvas().state.selectionChanged,
+ (changed) => {
+ if (changed) {
+ updateSelectionBounds()
+ canvasStore.getCanvas().state.selectionChanged = false
+
+ // Start transform sync if we have selection
+ if (visible.value) {
+ startSync()
+ } else {
+ stopSync()
+ }
+ }
+ },
+ { immediate: true }
+ )
+
+ // Watch for dragging state
+ watch(
+ () => canvasStore.canvas?.state?.draggingItems,
+ (dragging) => {
+ if (dragging) {
+ // Hide during node dragging
+ visible.value = false
+ } else {
+ // Update after dragging ends
+ requestAnimationFrame(() => {
+ updateSelectionBounds()
+ })
+ }
+ }
+ )
+
+ return {
+ visible
+ }
+}
diff --git a/src/extensions/core/index.ts b/src/extensions/core/index.ts
index 0d39c207ad..5354ef4e91 100644
--- a/src/extensions/core/index.ts
+++ b/src/extensions/core/index.ts
@@ -14,6 +14,7 @@ import './previewAny'
import './rerouteNode'
import './saveImageExtraOutput'
import './saveMesh'
+import './selectionBorder'
import './simpleTouchSupport'
import './slotDefaults'
import './uploadAudio'
diff --git a/src/extensions/core/selectionBorder.ts b/src/extensions/core/selectionBorder.ts
new file mode 100644
index 0000000000..c4afafad11
--- /dev/null
+++ b/src/extensions/core/selectionBorder.ts
@@ -0,0 +1,70 @@
+import { type LGraphCanvas, createBounds } from '@/lib/litegraph/src/litegraph'
+import { app } from '@/scripts/app'
+
+/**
+ * Draws a dashed border around selected items that maintains constant pixel size
+ * regardless of zoom level, similar to the DOM selection overlay.
+ */
+function drawSelectionBorder(
+ ctx: CanvasRenderingContext2D,
+ canvas: LGraphCanvas
+) {
+ const selectedItems = canvas.selectedItems
+
+ // Only draw if multiple items selected
+ if (selectedItems.size <= 1) return
+
+ // Use the same bounds calculation as the toolbox
+ const bounds = createBounds(selectedItems, 10)
+ if (!bounds) return
+
+ const [x, y, width, height] = bounds
+
+ // Save context state
+ ctx.save()
+
+ // Set up dashed line style that doesn't scale with zoom
+ const borderWidth = 2 / canvas.ds.scale // Constant 2px regardless of zoom
+ ctx.lineWidth = borderWidth
+ ctx.strokeStyle =
+ getComputedStyle(document.documentElement)
+ .getPropertyValue('--border-color')
+ .trim() || '#ffffff66'
+
+ // Create dash pattern that maintains visual size
+ const dashSize = 5 / canvas.ds.scale
+ ctx.setLineDash([dashSize, dashSize])
+
+ // Draw the border using the bounds directly
+ ctx.beginPath()
+ ctx.roundRect(x, y, width, height, 8 / canvas.ds.scale)
+ ctx.stroke()
+
+ // Restore context
+ ctx.restore()
+}
+
+/**
+ * Extension that adds a dashed selection border for multiple selected nodes
+ */
+const ext = {
+ name: 'Comfy.SelectionBorder',
+
+ async init() {
+ // Hook into the canvas drawing
+ const originalDrawForeground = app.canvas.onDrawForeground
+
+ app.canvas.onDrawForeground = function (
+ ctx: CanvasRenderingContext2D,
+ visibleArea: any
+ ) {
+ // Call original if it exists
+ originalDrawForeground?.call(this, ctx, visibleArea)
+
+ // Draw our selection border
+ drawSelectionBorder(ctx, app.canvas)
+ }
+ }
+}
+
+app.registerExtension(ext)
diff --git a/src/types/selectionOverlayTypes.ts b/src/types/selectionOverlayTypes.ts
deleted file mode 100644
index ee370e2748..0000000000
--- a/src/types/selectionOverlayTypes.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { InjectionKey, Ref } from 'vue'
-
-export interface SelectionOverlayState {
- visible: Readonly[>
- updateCount: Readonly][>
-}
-
-export const SelectionOverlayInjectionKey: InjectionKey =
- Symbol('selectionOverlayState')
]