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>
<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 the application/alpineflow MIME type.
  • The flow canvas listens for drop events with this MIME type and invokes the onDrop callback 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-controls or .flow-minimap — are cancelled, not forwarded to onDrop. 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 } };
    }
})