This example shows how to implement drag and drop functionality to add new nodes to your Vue Flow from an external source like a sidebar.
Live Demo
View the live demo on vueflow.dev.
Complete Example
< script lang = "ts" setup >
import { VueFlow , useVueFlow } from '@vue-flow/core'
import Sidebar from './Sidebar.vue'
let id = 0
function getId () {
return `dndnode_ ${ id ++ } `
}
const { onConnect , addEdges , addNodes , project } = useVueFlow ({
nodes: [
{
id: '1' ,
type: 'input' ,
label: 'input node' ,
position: { x: 250 , y: 5 },
},
],
})
function onDragOver ( event : DragEvent ) {
event . preventDefault ()
if ( event . dataTransfer ) {
event . dataTransfer . dropEffect = 'move'
}
}
const wrapper = ref ()
onConnect ( addEdges )
function onDrop ( event : DragEvent ) {
const type = event . dataTransfer ?. getData ( 'application/vueflow' )
const flowbounds = wrapper . value . $el . getBoundingClientRect ()
const position = project ({
x: event . clientX - flowbounds . left ,
y: event . clientY - flowbounds . top ,
})
addNodes ({
id: getId (),
type ,
position ,
label: ` ${ type } node` ,
})
}
</ script >
< template >
< div class = "dndflow" @ drop = " onDrop " >
< VueFlow ref = "wrapper" @ dragover = " onDragOver " />
< Sidebar />
</ div >
</ template >
< style >
.dndflow {
display : flex ;
flex-direction : column ;
height : 100 % ;
}
.vue-flow-wrapper {
flex : 1 ;
}
</ style >
Key Concepts
Drag Start
Set up the drag event with node type data:
function onDragStart ( event : DragEvent , nodeType : string ) {
if ( event . dataTransfer ) {
// Store the node type in the drag data
event . dataTransfer . setData ( 'application/vueflow' , nodeType )
event . dataTransfer . effectAllowed = 'move'
}
}
Drag Over
Prevent default behavior and set drop effect:
function onDragOver ( event : DragEvent ) {
event . preventDefault ()
if ( event . dataTransfer ) {
event . dataTransfer . dropEffect = 'move'
}
}
The dragover event must call preventDefault() to allow dropping.
Drop Handler
Handle the drop event and create a new node:
const { addNodes , project } = useVueFlow ()
const wrapper = ref ()
function onDrop ( event : DragEvent ) {
// Get the node type from drag data
const type = event . dataTransfer ?. getData ( 'application/vueflow' )
// Get the flow bounds
const flowbounds = wrapper . value . $el . getBoundingClientRect ()
// Calculate the position in flow coordinates
const position = project ({
x: event . clientX - flowbounds . left ,
y: event . clientY - flowbounds . top ,
})
// Add the new node
addNodes ({
id: getId (),
type ,
position ,
label: ` ${ type } node` ,
})
}
The project Function
The project function converts screen coordinates to flow coordinates, accounting for:
Zoom level
Pan position
Flow offset
const { project } = useVueFlow ()
// Convert screen coordinates to flow coordinates
const flowPosition = project ({ x: clientX , y: clientY })
Template Setup
< template >
< div class = "dndflow" @ drop = " onDrop " >
< VueFlow ref = "wrapper" @ dragover = " onDragOver " />
< Sidebar />
</ div >
</ template >
Wrapper Ref
Drop Container
Get a reference to the VueFlow component to access its bounding box: const wrapper = ref ()
const bounds = wrapper . value . $el . getBoundingClientRect ()
The drop event listener should be on a parent container: < div @ drop = " onDrop " >
<VueFlow @dragover="onDragOver" />
</ div >
Generating Node IDs
Create unique IDs for new nodes:
let id = 0
function getId () {
return `dndnode_ ${ id ++ } `
}
// Or use timestamps
function getId () {
return `node_ ${ Date . now () } `
}
// Or use UUIDs
import { v4 as uuidv4 } from 'uuid'
function getId () {
return uuidv4 ()
}
Draggable Elements
Make elements draggable:
< div
: draggable = " true "
@ dragstart = " ( event ) => onDragStart ( event , 'custom' ) "
>
Drag me
</ div >
Advanced Features
Drag with Custom Data
Pass additional data with the dragged item:
function onDragStart ( event : DragEvent , nodeData : any ) {
if ( event . dataTransfer ) {
event . dataTransfer . setData ( 'application/vueflow' , JSON . stringify ( nodeData ))
event . dataTransfer . effectAllowed = 'move'
}
}
function onDrop ( event : DragEvent ) {
const nodeData = JSON . parse ( event . dataTransfer ?. getData ( 'application/vueflow' ) || '{}' )
const position = project ({
x: event . clientX - flowbounds . left ,
y: event . clientY - flowbounds . top ,
})
addNodes ({
id: getId (),
... nodeData ,
position ,
})
}
Visual Drag Feedback
Style elements during drag:
.draggable {
cursor : grab ;
transition : opacity 0.2 s ;
}
.draggable:active {
cursor : grabbing ;
opacity : 0.5 ;
}
Prevent Drag on Certain Elements
Use the nodrag class:
< div class = "nodrag" >
<button>I won't trigger drag</button>
</ div >
Complete Workflow
User starts dragging from sidebar
onDragStart stores node type in dataTransfer
User drags over the flow
onDragOver prevents default and sets drop effect
User drops the node
onDrop reads the node type
Calculates position using project
Creates new node with addNodes
TypeScript Types
import type { Node } from '@vue-flow/core'
interface DraggableNodeData {
type : string
label ?: string
data ?: any
}
function onDragStart ( event : DragEvent , nodeData : DraggableNodeData ) {
// ...
}
function onDrop ( event : DragEvent ) : void {
// ...
}
Example with Custom Node Types
< script setup >
import CustomNode from './CustomNode.vue'
function onDragStart ( event : DragEvent , config : any ) {
event . dataTransfer ?. setData ( 'application/vueflow' , JSON . stringify ( config ))
}
function onDrop ( event : DragEvent ) {
const config = JSON . parse ( event . dataTransfer ?. getData ( 'application/vueflow' ) || '{}' )
addNodes ({
id: getId (),
type: config . type ,
position: project ({ x: event . clientX , y: event . clientY }),
data: config . data ,
})
}
</ script >
< template >
< VueFlow >
< template # node-custom = " props " >
< CustomNode v-bind = " props " />
</ template >
</ VueFlow >
</ template >
Best Practices
Always use project() to convert screen coordinates to flow coordinates
Generate unique IDs for each new node
Provide visual feedback during drag operations
Handle edge cases (invalid drops, missing data)
Consider accessibility (keyboard alternatives)
Next Steps
Save & Restore Save and restore flow state
Custom Nodes Create custom node types
useVueFlow Learn about the useVueFlow composable
Node API Complete node type reference