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.
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.
<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.
<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.
<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.
<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.
<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.