Skip to main content
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>
Get a reference to the VueFlow component to access its bounding box:
const wrapper = ref()

const bounds = wrapper.value.$el.getBoundingClientRect()

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.2s;
}

.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

  1. User starts dragging from sidebar
    • onDragStart stores node type in dataTransfer
  2. User drags over the flow
    • onDragOver prevents default and sets drop effect
  3. 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>
<div class="layout">
  <aside class="sidebar"><!-- draggable items --></aside>
  <VueFlow />
</div>

<style>
.layout {
  display: flex;
  height: 100%;
}
.sidebar {
  width: 200px;
  border-right: 1px solid #eee;
}
</style>

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

Build docs developers (and LLMs) love