Skip to content

Flow

FluxFlow is the root of a flow: a shared coordinate space that connectors use to route between nodes. By default it lays its content out naturally, sizes its own height, and scrolls horizontally when the content is wider than its container, so it drops inline without a fixed-height wrapper.

Add interactive to opt into a pannable and zoomable viewport that fills its container instead: drag or scroll with two fingers to pan, and pinch or hold ctrl/cmd while scrolling to zoom towards the cursor.

New Campaign Request
Starts automatically when a marketing campaign brief is submitted.
Webhook
Analyze Market
Collects competitor insights and analyzes market trends.
Opus 4.8
394 tokens
Draft Content
Generates campaign copy variations from the strategy brief.
Opus 4.8
512 tokens
Send Campaign
Delivers the finished campaign to the marketing distribution list.
Email

TIP

Everything inside a flow, both FluxFlowNode and FluxFlowConnection, is written declaratively. Connectors reference nodes by id, so cards stay free of wiring props.

Props

align?: 'start' | 'center'
Where an interactive viewport opens horizontally. `center` centres the flow when the container is wider than it, and falls back to `start` when it is not; `start` always sits against the flow's left edge. Both open at the top.
Default: center

axis?: 'horizontal' | 'vertical'
The axis every connector leaves and enters its nodes on. Without one each connector picks the shorter of the two, which is the wrong one as soon as a row of nodes is wider than the gap between two rows. A connector that names its own from-side or to-side still wins.

background?: 'dots' | 'grid' | 'none'
The backdrop behind the flow. `dots` and `grid` pan with the content at a fixed size; `none` is transparent. Opt in by setting it.
Default: none

interactive?: boolean
Fills its container with a pannable and zoomable viewport, starting at 100% zoom from the top of the flow. Without it the flow sizes its own height to its content and scrolls when wider than its container.

start?: string
The id of a node to centre the interactive viewport on at 100% zoom, instead of starting at the top of the flow.

padding?: number
The padding, in pixels, kept around the content.

minZoom?: number
The minimum zoom level.
Default: 0.4

maxZoom?: number
The maximum zoom level.
Default: 2

zoomStep?: number
The relative amount each zoom step changes the zoom level.
Default: 0.2

gridSize?: number
The spacing, in pixels, of the background dots or grid at 100% zoom.
Default: 24

viewport?: FluxFlowViewport
The current viewport (x, y and zoom). Use with v-model:viewport for a controlled viewport.

Emits

update:viewport: [FluxFlowViewport]
Triggered when the viewport pans or zooms. Bind it with v-model:viewport to control the viewport.

Slots

default
The nodes and connectors that make up the flow. Place FluxFlowNode and FluxFlowConnection components here.

Examples

Branching

A condition that fans out to two labeled branches. Branching is just more connectors, each referencing a node by id.

Payment Received
Amount over €1,000?
Manual Review
Auto-approve

<template>
    <FluxFlow :padding="21">
        <FluxFlowNode id="trigger" :x="210" :y="0">
            <FluxFlowTriggerCard title="Payment Received"/>
        </FluxFlowNode>

        <FluxFlowNode id="check" :x="210" :y="216">
            <FluxFlowConditionCard title="Amount over €1,000?"/>
        </FluxFlowNode>

        <FluxFlowNode id="review" :x="0" :y="426">
            <FluxFlowActionCard title="Manual Review" icon="gauge" color="warning"/>
        </FluxFlowNode>

        <FluxFlowNode id="approve" :x="420" :y="426">
            <FluxFlowActionCard title="Auto-approve" icon="circle-check" color="success"/>
        </FluxFlowNode>

        <FluxFlowConnection from="trigger" to="check"/>
        <FluxFlowConnection from="check" to="review" label="Yes"/>
        <FluxFlowConnection from="check" to="approve" label="No"/>
    </FluxFlow>
</template>

<script
    setup
    lang="ts">
    import { FluxFlow, FluxFlowActionCard, FluxFlowConditionCard, FluxFlowConnection, FluxFlowNode, FluxFlowTriggerCard } from '@flux-ui/flow';
</script>

Axis

Two branches sitting further apart than the row below them is deep. Without axis each connector would take the shorter way out, leaving the card sideways and crossing back over it; naming the axis keeps every line running with the flow.

Ticket raised
Route to billing
Route to support

<template>
    <FluxFlow
        axis="vertical"
        :padding="21">
        <FluxFlowNode id="intake" :x="270" :y="0">
            <FluxFlowTriggerCard title="Ticket raised" icon="bell"/>
        </FluxFlowNode>

        <FluxFlowNode id="billing" :x="0" :y="150">
            <FluxFlowActionCard title="Route to billing" icon="money-bill"/>
        </FluxFlowNode>

        <FluxFlowNode id="support" :x="540" :y="150">
            <FluxFlowActionCard title="Route to support" icon="envelope"/>
        </FluxFlowNode>

        <FluxFlowConnection from="intake" to="billing"/>
        <FluxFlowConnection from="intake" to="support"/>
    </FluxFlow>
</template>

<script
    setup
    lang="ts">
    import { FluxFlow, FluxFlowActionCard, FluxFlowConnection, FluxFlowNode, FluxFlowTriggerCard } from '@flux-ui/flow';
</script>

Background

Opt into a dots or grid backdrop with the background prop; it is transparent by default.

Order Placed
Send Confirmation

<template>
    <FluxFlow :padding="21" background="dots">
        <FluxFlowNode id="order" :x="0" :y="0">
            <FluxFlowTriggerCard title="Order Placed"/>
        </FluxFlowNode>

        <FluxFlowNode id="confirm" :x="390" :y="0">
            <FluxFlowActionCard title="Send Confirmation" icon="envelope" color="info"/>
        </FluxFlowNode>

        <FluxFlowConnection from="order" to="confirm"/>
    </FluxFlow>
</template>

<script
    setup
    lang="ts">
    import { FluxFlow, FluxFlowActionCard, FluxFlowConnection, FluxFlowNode, FluxFlowTriggerCard } from '@flux-ui/flow';
</script>

Interactive

With interactive the flow fills its container as a pannable, zoomable canvas, starting at 100% from the top of the flow. Drag or scroll with two fingers to pan, and pinch or hold ctrl/cmd while scrolling to zoom.

Payment Received
Amount over €1,000?
Manual Review
Auto-approve

<template>
    <div style="height: 390px">
        <FluxFlow :padding="21" interactive background="dots">
            <FluxFlowNode id="trigger" :x="210" :y="0">
                <FluxFlowTriggerCard title="Payment Received"/>
            </FluxFlowNode>

            <FluxFlowNode id="check" :x="210" :y="216">
                <FluxFlowConditionCard title="Amount over €1,000?"/>
            </FluxFlowNode>

            <FluxFlowNode id="review" :x="0" :y="426">
                <FluxFlowActionCard title="Manual Review" icon="gauge" color="warning"/>
            </FluxFlowNode>

            <FluxFlowNode id="approve" :x="420" :y="426">
                <FluxFlowActionCard title="Auto-approve" icon="circle-check" color="success"/>
            </FluxFlowNode>

            <FluxFlowConnection from="trigger" to="check"/>
            <FluxFlowConnection from="check" to="review" label="Yes"/>
            <FluxFlowConnection from="check" to="approve" label="No"/>
        </FluxFlow>
    </div>
</template>

<script
    setup
    lang="ts">
    import { FluxFlow, FluxFlowActionCard, FluxFlowConditionCard, FluxFlowConnection, FluxFlowNode, FluxFlowTriggerCard } from '@flux-ui/flow';
</script>

Start point

Point start at a node id to open the interactive viewport centered on that card instead of the top of the flow.

Payment Received
Amount over €1,000?
Manual Review
Auto-approve

<template>
    <div style="height: 390px">
        <FluxFlow interactive start="review" :padding="21" background="dots">
            <FluxFlowNode id="trigger" :x="210" :y="0">
                <FluxFlowTriggerCard title="Payment Received"/>
            </FluxFlowNode>

            <FluxFlowNode id="check" :x="210" :y="216">
                <FluxFlowConditionCard title="Amount over €1,000?"/>
            </FluxFlowNode>

            <FluxFlowNode id="review" :x="0" :y="426">
                <FluxFlowActionCard title="Manual Review" icon="gauge" color="warning"/>
            </FluxFlowNode>

            <FluxFlowNode id="approve" :x="420" :y="426">
                <FluxFlowActionCard title="Auto-approve" icon="circle-check" color="success"/>
            </FluxFlowNode>

            <FluxFlowConnection from="trigger" to="check"/>
            <FluxFlowConnection from="check" to="review" label="Yes"/>
            <FluxFlowConnection from="check" to="approve" label="No"/>
        </FluxFlow>
    </div>
</template>

<script
    setup
    lang="ts">
    import { FluxFlow, FluxFlowActionCard, FluxFlowConditionCard, FluxFlowConnection, FluxFlowNode, FluxFlowTriggerCard } from '@flux-ui/flow';
</script>

TIP

On an interactive canvas, controls inside a card (a toggle, a button, a link, a text field) keep working: a press that starts on one never begins a pan. Add data-nopan to any other element that should grab the pointer for itself instead of panning the canvas.

TIP

An interactive canvas is reachable with the keyboard: tab to it, then pan with the arrow keys (hold shift to cover three times the ground), zoom with + and -, and press 0 to fit the whole flow in view. A field or a button inside a card keeps every key it is given.

TIP

An interactive canvas pans up to 300px past the viewport and then stops, so it always has room to move, even when the flow is smaller than its container. A two-finger scroll that runs into that edge hands the page its scroll back, so a flow embedded halfway down a page never traps it.

For flows built out in full, from routing rules to a running deploy pipeline, see Examples.

Used components