Drag from Sidebar
The x-flow-draggable directive creates a draggable element that can be dropped onto the flow canvas to add new nodes. It is typically used in a sidebar or palette outside the canvas.
Drag items from the palette onto the canvas:
INTERACTIVE
Input
Process
Output
<div x-data style="display: flex; gap: 8px;">
<div style="display: flex; flex-direction: column; gap: 4px; min-width: 90px;">
<div x-flow-draggable="'input'" class="rounded border border-border-subtle bg-elevated px-3 py-2 font-mono text-[11px] text-text-muted cursor-grab" draggable="true">Input</div>
<div x-flow-draggable="'process'" class="rounded border border-border-subtle bg-elevated px-3 py-2 font-mono text-[11px] text-text-muted cursor-grab" draggable="true">Process</div>
<div x-flow-draggable="'output'" class="rounded border border-border-subtle bg-elevated px-3 py-2 font-mono text-[11px] text-text-muted cursor-grab" draggable="true">Output</div>
</div>
<div x-data="flowCanvas({
nodes: [
{ id: 'start', position: { x: 50, y: 60 }, data: { label: 'Start' } },
],
edges: [],
background: 'dots',
fitViewOnInit: true,
controls: false,
pannable: false,
zoomable: false,
onDrop({ data, position }) {
return { id: 'drop-' + Date.now(), position, data: { label: data } };
},
})" class="flow-container" style="height: 220px; flex: 1;">
<div x-flow-viewport>
<template x-for="node in nodes" :key="node.id">
<div x-flow-node="node">
<div x-flow-handle:target></div>
<span x-text="node.data.label"></span>
<div x-flow-handle:source></div>
</div>
</template>
</div>
</div>
</div>
x-flow-draggable Directive
Expression
The expression provides the data to attach to the drag event. This can be a string or an object and will be available in the canvas onDrop callback:
<!-- String -->
<div x-flow-draggable="'input-node'">Input Node</div>
<!-- Object -->
<div x-flow-draggable="{ type: 'database', label: 'Users Table' }">Database Node</div>
Sidebar Palette Pattern
<aside class="node-palette">
<h2>Nodes</h2>
<div x-flow-draggable="'input'">Input</div>
<div x-flow-draggable="'transform'">Transform</div>
<div x-flow-draggable="'output'">Output</div>
</aside>
<div x-data="flowCanvas({
onDrop({ data, position }) {
return {
id: crypto.randomUUID(),
position,
data: { label: data, type: data },
};
}
})">
<!-- canvas content -->
</div>
Object Data
When the expression evaluates to an object, the full object is available in onDrop:
<div x-flow-draggable="{ type: 'api', method: 'GET', label: 'API Call' }">
API Call
</div>
onDrop({ data, position }) {
// data = { type: 'api', method: 'GET', label: 'API Call' }
return {
id: crypto.randomUUID(),
position,
data,
};
}
Drag items with object data — the label and type are passed through:
INTERACTIVE
API Call
Database
<div x-data style="display: flex; gap: 8px;">
<div style="display: flex; flex-direction: column; gap: 4px; min-width: 90px;">
<div x-flow-draggable="{ type: 'api', label: 'GET /users' }" class="rounded border border-border-subtle bg-elevated px-3 py-2 font-mono text-[11px] text-text-muted cursor-grab" draggable="true">API Call</div>
<div x-flow-draggable="{ type: 'db', label: 'Users Table' }" class="rounded border border-border-subtle bg-elevated px-3 py-2 font-mono text-[11px] text-text-muted cursor-grab" draggable="true">Database</div>
</div>
<div x-data="flowCanvas({
nodes: [],
edges: [],
background: 'dots',
fitViewOnInit: false,
controls: false,
pannable: false,
zoomable: false,
onDrop({ data, position }) {
return { id: 'drop-' + Date.now(), position, data: { label: data.label } };
},
})" class="flow-container" style="height: 220px; flex: 1;">
<div x-flow-viewport>
<template x-for="node in nodes" :key="node.id">
<div x-flow-node="node">
<div x-flow-handle:target></div>
<span x-text="node.data.label"></span>
<div x-flow-handle:source></div>
</div>
</template>
</div>
</div>
</div>
Behavior
- Sets
draggable="true"on the element. - On
dragstart, serializes the expression value and attaches it to the drag event using theapplication/alpineflowMIME type. - The flow canvas listens for
dropevents with this MIME type and invokes theonDropcallback with the deserialized data and the drop position in canvas coordinates. - Drops released over a floating overlay — a
.flow-panel(which is where an in-canvas palette lives),.flow-controlsor.flow-minimap— are cancelled, not forwarded toonDrop. Those overlays sit above the canvas surface, so releasing an item back onto the palette you dragged it from reads as "never mind" rather than as a drop at the position behind it. The browser shows the "no drop" cursor while over them.
Canvas Configuration: onDrop
The onDrop callback must be defined in the flowCanvas configuration for dropped items to create nodes:
flowCanvas({
nodes: [],
edges: [],
onDrop({ event, data, position, targetNode }) {
// `event` - the native DragEvent
// `data` - the deserialized expression value from x-flow-draggable
// `position` - { x, y } in canvas coordinates
// `targetNode` - the node under the cursor, or null
return { id: 'new', position, data: { label: data } };
}
})