diff --git a/docs/examples/dnd/App.vue b/docs/examples/dnd/App.vue
index 94c836e3..1c1bd16c 100644
--- a/docs/examples/dnd/App.vue
+++ b/docs/examples/dnd/App.vue
@@ -1,73 +1,31 @@
-
+
+
+
+
diff --git a/docs/examples/dnd/DropzoneBackground.vue b/docs/examples/dnd/DropzoneBackground.vue
new file mode 100644
index 00000000..a5c4439b
--- /dev/null
+++ b/docs/examples/dnd/DropzoneBackground.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/examples/dnd/Sidebar.vue b/docs/examples/dnd/Sidebar.vue
index e1ba6028..62591f4b 100644
--- a/docs/examples/dnd/Sidebar.vue
+++ b/docs/examples/dnd/Sidebar.vue
@@ -1,10 +1,7 @@
diff --git a/docs/examples/dnd/index.ts b/docs/examples/dnd/index.ts
index ef338dd7..8b22f465 100644
--- a/docs/examples/dnd/index.ts
+++ b/docs/examples/dnd/index.ts
@@ -1,3 +1,5 @@
export { default as DndApp } from './App.vue?raw'
export { default as DndSidebar } from './Sidebar.vue?raw'
+export { default as DndBackground } from './DropzoneBackground.vue?raw'
export { default as DndCSS } from './style.css?inline'
+export { default as DndScript } from './useDnD.js?raw'
diff --git a/docs/examples/dnd/useDnD.js b/docs/examples/dnd/useDnD.js
new file mode 100644
index 00000000..b13f2cc1
--- /dev/null
+++ b/docs/examples/dnd/useDnD.js
@@ -0,0 +1,120 @@
+import { useVueFlow } from '@vue-flow/core'
+import { ref, watch } from 'vue'
+
+let id = 0
+
+/**
+ * @returns {string} - A unique id.
+ */
+function getId() {
+ return `dndnode_${id++}`
+}
+
+/**
+ * In a real world scenario you'd want to avoid creating refs in a global scope like this as they might not be cleaned up properly.
+ * @type {{draggedType: Ref, isDragOver: Ref, isDragging: Ref}}
+ */
+const state = {
+ /**
+ * The type of the node being dragged.
+ */
+ draggedType: ref(null),
+ isDragOver: ref(false),
+ isDragging: ref(false),
+}
+
+export default function useDragAndDrop() {
+ const { draggedType, isDragOver, isDragging } = state
+
+ const { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow()
+
+ watch(isDragging, (dragging) => {
+ document.body.style.userSelect = dragging ? 'none' : ''
+ })
+
+ function onDragStart(event, type) {
+ if (event.dataTransfer) {
+ event.dataTransfer.setData('application/vueflow', type)
+ event.dataTransfer.effectAllowed = 'move'
+ }
+
+ draggedType.value = type
+ isDragging.value = true
+
+ document.addEventListener('drop', onDragEnd)
+ }
+
+ /**
+ * Handles the drag over event.
+ *
+ * @param {DragEvent} event
+ */
+ function onDragOver(event) {
+ event.preventDefault()
+
+ if (draggedType.value) {
+ isDragOver.value = true
+
+ if (event.dataTransfer) {
+ event.dataTransfer.dropEffect = 'move'
+ }
+ }
+ }
+
+ function onDragLeave() {
+ isDragOver.value = false
+ }
+
+ function onDragEnd() {
+ isDragging.value = false
+ isDragOver.value = false
+ draggedType.value = null
+ document.removeEventListener('drop', onDragEnd)
+ }
+
+ /**
+ * Handles the drop event.
+ *
+ * @param {DragEvent} event
+ */
+ function onDrop(event) {
+ const position = screenToFlowCoordinate({
+ x: event.clientX,
+ y: event.clientY,
+ })
+
+ const nodeId = getId()
+
+ const newNode = {
+ id: nodeId,
+ type: draggedType.value,
+ position,
+ label: `[${nodeId}]`,
+ }
+
+ /**
+ * Align node position after drop, so it's centered to the mouse
+ *
+ * We can hook into events even in a callback, and we can remove the event listener after it's been called.
+ */
+ const { off } = onNodesInitialized(() => {
+ updateNode(nodeId, (node) => ({
+ position: { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 },
+ }))
+
+ off()
+ })
+
+ addNodes(newNode)
+ }
+
+ return {
+ draggedType,
+ isDragOver,
+ isDragging,
+ onDragStart,
+ onDragLeave,
+ onDragOver,
+ onDrop,
+ }
+}
diff --git a/docs/examples/edges/App.vue b/docs/examples/edges/App.vue
index 153df012..6010650e 100644
--- a/docs/examples/edges/App.vue
+++ b/docs/examples/edges/App.vue
@@ -1,12 +1,12 @@
-
+
diff --git a/docs/examples/index.ts b/docs/examples/index.ts
index ba44395a..777baf5c 100644
--- a/docs/examples/index.ts
+++ b/docs/examples/index.ts
@@ -8,7 +8,7 @@ import { UpdateEdgeApp } from './update-edge'
import { UpdateNodeApp, UpdateNodeCSS } from './update-node'
import { ValidationApp, ValidationCSS, ValidationCustomInput, ValidationCustomNode } from './validation'
import { SaveRestoreApp, SaveRestoreCSS, SaveRestoreControls } from './save-restore'
-import { DndApp, DndCSS, DndSidebar } from './dnd'
+import { DndApp, DndBackground, DndCSS, DndScript, DndSidebar } from './dnd'
import { EmptyApp } from './empty'
import { HiddenApp } from './hidden'
import { InteractionApp, InteractionCSS, InteractionControls } from './interaction'
@@ -75,7 +75,9 @@ export const exampleImports = {
dnd: {
'App.vue': DndApp,
'Sidebar.vue': DndSidebar,
+ 'DropzoneBackground.vue': DndBackground,
'style.css': DndCSS,
+ 'useDnD.js': DndScript,
},
empty: {
'App.vue': EmptyApp,