Skip to main content

Handles

Handles are the connection points on nodes that allow users to create edges by dragging from one handle to another. They are essential for building interactive flow diagrams.

Handle Component

The <Handle> component from @vue-flow/core creates connection points on your custom nodes:
<script setup>
import { Handle, Position } from '@vue-flow/core'
</script>

<template>
  <div class="custom-node">
    <Handle 
      type="target" 
      :position="Position.Left" 
    />
    
    <div class="content">
      Node Content
    </div>
    
    <Handle 
      type="source" 
      :position="Position.Right" 
    />
  </div>
</template>
The <Handle> component only works inside node components. Using it outside this context will not function correctly.

Handle Types

Handles have two types that determine connection direction:
source
HandleType
Source handles are the starting point of connections. Users drag from source handles to create new edges.
target
HandleType
Target handles are the ending point of connections. Users drag to target handles to complete connections.
<template>
  <!-- Source: connections start here -->
  <Handle type="source" :position="Position.Right" />
  
  <!-- Target: connections end here -->
  <Handle type="target" :position="Position.Left" />
</template>

Handle Positions

Handles can be placed on any side of a node using the Position enum:
import { Position } from '@vue-flow/core'

<Handle 
  type="target" 
  :position="Position.Left" 
/>
Typically used for target handles on nodes that receive input.
Handle position affects edge curve direction. A Position.Right source creates edges that curve toward the right, while a Position.Left target creates edges that curve from the left.

Handle Props

type
'source' | 'target'
required
Determines if this handle starts or ends connections
position
Position
required
Side of the node where handle is placed
id
string
Unique identifier when using multiple handles of the same type. Required when a node has multiple source or target handles.
connectable
boolean | number | 'single' | HandleConnectableFunc
default:"true"
Controls connection behavior:
  • true: Unlimited connections
  • false: No connections allowed
  • number: Maximum number of connections
  • 'single': Only one connection
  • function: Custom validation logic
connectableStart
boolean
default:"true"
Whether connections can start from this handle (for source handles)
connectableEnd
boolean
default:"true"
Whether connections can end at this handle (for target handles)
isValidConnection
ValidConnectionFunc
Function to validate if a connection attempt is allowed

Multiple Handles

Nodes can have any number of handles. When using multiple handles of the same type, each must have a unique id:
<template>
  <div class="node-with-multiple-handles">
    <!-- Multiple source handles -->
    <Handle 
      id="source-a" 
      type="source" 
      :position="Position.Right"
      style="top: 25%"
    />
    <Handle 
      id="source-b" 
      type="source" 
      :position="Position.Right"
      style="top: 75%"
    />
    
    <!-- Multiple target handles -->
    <Handle 
      id="target-a" 
      type="target" 
      :position="Position.Left"
      style="top: 25%"
    />
    <Handle 
      id="target-b" 
      type="target" 
      :position="Position.Left"
      style="top: 75%"
    />
    
    <div class="content">
      Multi-Handle Node
    </div>
  </div>
</template>

Connecting to Specific Handles

When creating edges, specify which handles to connect:
const edge = {
  id: 'e1-2',
  source: '1',
  target: '2',
  sourceHandle: 'source-a', // Connect from specific source handle
  targetHandle: 'target-b'  // Connect to specific target handle
}
In connection events:
import { useVueFlow } from '@vue-flow/core'

const { onConnect } = useVueFlow()

onConnect((connection) => {
  console.log('Source node:', connection.source)
  console.log('Source handle:', connection.sourceHandle) // or null
  console.log('Target node:', connection.target)
  console.log('Target handle:', connection.targetHandle) // or null
})

Handle Positioning with CSS

Handles use absolute positioning. Customize their location with CSS:
<template>
  <div class="custom-node">
    <!-- Top handle -->
    <Handle 
      type="target" 
      :position="Position.Right"
      style="top: 10px"
    />
    
    <!-- Bottom handle -->
    <Handle 
      type="source" 
      :position="Position.Right"
      style="bottom: 10px; top: auto;"
    />
  </div>
</template>

Hidden Handles

Create invisible handles for connections without visual indicators:
<template>
  <Handle 
    type="source" 
    :position="Position.Right"
    style="opacity: 0"
  />
</template>
Do not use v-if or v-show to hide handles. This breaks edge calculations. Always use opacity: 0 instead.

Connection Limits

Control how many connections a handle can have:
<!-- Allow unlimited connections -->
<Handle type="source" :position="Position.Right" :connectable="true" />

<!-- Prevent all connections -->
<Handle type="target" :position="Position.Left" :connectable="false" />

Connection Validation

Validate connections before they’re created:
<script setup>
import type { Connection, ValidConnectionFunc } from '@vue-flow/core'

const isValidConnection: ValidConnectionFunc = (
  connection: Connection,
  elements
) => {
  const { sourceNode, targetNode, edges } = elements
  
  // Prevent self-connections
  if (connection.source === connection.target) {
    return false
  }
  
  // Prevent duplicate connections
  const isDuplicate = edges.some(
    edge => 
      edge.source === connection.source && 
      edge.target === connection.target
  )
  
  if (isDuplicate) {
    return false
  }
  
  // Custom validation logic
  return sourceNode.data.type === 'output' && 
         targetNode.data.type === 'input'
}
</script>

<template>
  <Handle 
    type="source" 
    :position="Position.Right"
    :is-valid-connection="isValidConnection"
  />
</template>

Connection Mode

Control handle-to-handle connection rules:
<script setup>
import { ConnectionMode, VueFlow } from '@vue-flow/core'
</script>

<template>
  <!-- Loose: Allow source-to-source, target-to-target connections -->
  <VueFlow :connection-mode="ConnectionMode.Loose" />
  
  <!-- Strict: Only allow source-to-target connections -->
  <VueFlow :connection-mode="ConnectionMode.Strict" />
</template>
In Loose mode, Vue Flow automatically swaps source/target when connecting incompatible handles. In Strict mode, invalid connections are rejected.

Dynamic Handles

Add or update handles dynamically:
<script setup>
import { ref } from 'vue'
import { Position, Handle, useVueFlow } from '@vue-flow/core'

const { updateNodeInternals } = useVueFlow()
const handleCount = ref(2)

function addHandle() {
  handleCount.value++
  // Update node internals after adding handles
  updateNodeInternals(['node-id'])
}
</script>

<template>
  <div class="dynamic-node">
    <Handle
      v-for="i in handleCount"
      :key="i"
      :id="`handle-${i}`"
      type="source"
      :position="Position.Right"
      :style="{ top: `${(i / (handleCount + 1)) * 100}%` }"
    />
    
    <button @click="addHandle">Add Handle</button>
  </div>
</template>
In Vue Flow 1.x, handles auto-attach on mount. Only call updateNodeInternals if handles aren’t appearing correctly.

Handle Styling

Customize handle appearance:
<template>
  <Handle
    type="source"
    :position="Position.Right"
    :style="{
      width: '16px',
      height: '16px',
      background: '#4f46e5',
      border: '3px solid white',
      borderRadius: '50%'
    }"
  />
</template>

<style>
/* Global handle styles */
.vue-flow__handle {
  transition: all 0.2s ease;
}

.vue-flow__handle:hover {
  transform: scale(1.2);
  box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.2);
}

.vue-flow__handle-connecting {
  background: #22c55e !important;
}

.vue-flow__handle-valid {
  background: #22c55e !important;
}
</style>

Handle Events

Handles don’t have direct event handlers, but you can listen to connection events:
<script setup>
import { useVueFlow } from '@vue-flow/core'

const { onConnectStart, onConnect, onConnectEnd } = useVueFlow()

onConnectStart((event) => {
  console.log('Connection started', event)
})

onConnect((connection) => {
  console.log('Connection created', connection)
})

onConnectEnd(() => {
  console.log('Connection ended')
})
</script>

HandleConnectable Function

Implement complex connection logic:
import type { HandleConnectableFunc, GraphNode, GraphEdge } from '@vue-flow/core'

const handleConnectable: HandleConnectableFunc = (
  node: GraphNode,
  connectedEdges: GraphEdge[]
) => {
  // Allow connections based on node type
  if (node.type === 'input') {
    return connectedEdges.length < 1
  }
  
  // Check edge types
  const dataEdges = connectedEdges.filter(
    edge => edge.data?.type === 'data'
  )
  
  // Allow unlimited control edges, but max 3 data edges
  return dataEdges.length < 3
}
<template>
  <Handle
    type="source"
    :position="Position.Right"
    :connectable="handleConnectable"
  />
</template>

See Also

  • Nodes - Learn about creating custom nodes
  • Edges - Understand edge types and connections
  • State Management - Access handle connections in state

Build docs developers (and LLMs) love