Connection
FluxFlowConnection draws a line between two nodes. It references them by from and to, resolves their positions from the flow, and routes a path between the nearest edges. Because connectors are declared separately from the nodes, all of a flow's wiring lives in one place.
By default the connector picks the most natural pair of sides based on where the nodes sit: stacked nodes connect bottom to top, side by side nodes connect right to left. Override from-side / to-side when you need an explicit route, for example the two branches out of a condition. Both branches then leave the same edge in the same spot; from-align / to-align move either end to the start or the end of its side to pull them apart, aligned on the icon of the node they touch.
For a branch that belongs to something written inside the node, a Port is the sharper tool: from-port / to-port land the connector on the row that names the outcome.
A connector may also point at the node it left. Give from and to the same id and it loops out of one side and back into that same side, which is how a retry says it runs the step again. Name both from-side and to-side and the loop routes around the node from the one to the other instead.
Props
from: string
The id of the source node.
to: string
The id of the target node.
fromSide?: 'top' | 'right' | 'bottom' | 'left'
The side of the source node the connector leaves from. Defaults to the most natural side based on the relative position of the nodes.
toSide?: 'top' | 'right' | 'bottom' | 'left'
The side of the target node the connector enters. Defaults to the most natural side based on the relative position of the nodes.
fromAlign?: 'start' | 'center' | 'end'
Where along its side the connector leaves the source node. `start` lands on the node's own icon, `end` mirrors it.
Default: center
toAlign?: 'start' | 'center' | 'end'
Where along its side the connector enters the target node. `start` lands on the node's own icon, `end` mirrors it.
Default: center
fromPort?: string
The id of a FluxFlowPort inside the source node. The port fixes where along the side the connector leaves, which takes the place of fromAlign. An id that no port answers to falls back to the node's own anchor.
toPort?: string
The id of a FluxFlowPort inside the target node. The port fixes where along the side the connector enters, which takes the place of toAlign. An id that no port answers to falls back to the node's own anchor.
waypoints?: {x: number, y: number}[]
Points the connector runs through, in flow coordinates, the same space as the x / y of a node.
type?: 'smoothstep' | 'step' | 'bezier' | 'straight'
The shape of the connector.
Default: smoothstep
color?: FluxColor | string
The color of the base line. Accepts a FluxColor or any CSS color.
label?: string
A label rendered in a badge at the midpoint of the connector.
labelPlacement?: 'center' | 'first-leg' | 'last-leg'
Where the badge rides on a connector that bends. `center` is halfway along the run, `first-leg` and `last-leg` move it to the leg leaving the source or entering the target. A connector without a bend always keeps its badge in the middle.
Default: center
icon?: FluxIconName
An icon rendered at the midpoint of the connector, breaking the line. Next to a label it sits in the same badge; on its own it renders standalone on the line, without a badge background.
dashed?: boolean
Draws the base line dashed instead of solid.
dotted?: boolean
Draws the base line dotted instead of solid.
animated?: boolean
Travels the dashes or dots along the line, towards the target. Only affects a dashed or dotted connector.
markerStart?: 'arrow' | 'bar' | 'chevron' | 'diamond' | 'dot' | 'square' | 'none'
What the connector renders where it leaves the source node.
Default: dot
markerEnd?: 'arrow' | 'bar' | 'chevron' | 'diamond' | 'dot' | 'square' | 'none'
What the connector renders where it reaches the target node.
Default: chevron
progressColor?: FluxColor | string
The color of the progress overlay. Accepts a FluxColor or any CSS color.
Default: primary
progressValue?: number
How much of the connector is filled with the progress color, from 0 to 1. Set it to visualize a running flow.
Examples
Types
Four connector shapes: smoothstep (the default, orthogonal with rounded corners), step (the same route with sharp corners), bezier and straight.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="smooth-top" :x="0" :y="0">
<FluxFlowCard title="Smoothstep"/>
</FluxFlowNode>
<FluxFlowNode id="smooth-bottom" :x="120" :y="210">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="bezier-top" :x="420" :y="0">
<FluxFlowCard title="Bezier"/>
</FluxFlowNode>
<FluxFlowNode id="bezier-bottom" :x="540" :y="210">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="step-top" :x="840" :y="0">
<FluxFlowCard title="Step"/>
</FluxFlowNode>
<FluxFlowNode id="step-bottom" :x="960" :y="210">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="straight-top" :x="1260" :y="0">
<FluxFlowCard title="Straight"/>
</FluxFlowNode>
<FluxFlowNode id="straight-bottom" :x="1380" :y="210">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowConnection from="smooth-top" to="smooth-bottom" type="smoothstep"/>
<FluxFlowConnection from="bezier-top" to="bezier-bottom" type="bezier"/>
<FluxFlowConnection from="step-top" to="step-bottom" type="step"/>
<FluxFlowConnection from="straight-top" to="straight-bottom" type="straight"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>Progress
A connector with a progress-value fills from the source towards the target, which is useful for visualizing a running flow. The fill animates as the value changes and respects reduced motion.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="extract" :x="0" :y="0">
<FluxFlowActionCard title="Extract" label="Running" icon="gauge" color="primary" active/>
</FluxFlowNode>
<FluxFlowNode id="load" :x="0" :y="180">
<FluxFlowActionCard title="Load"/>
</FluxFlowNode>
<FluxFlowConnection from="extract" to="load" progress-color="primary" :progress-value="progress"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowActionCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
import { onBeforeUnmount, onMounted, ref } from 'vue';
const progress = ref(0);
let frame = 0;
onMounted(() => {
const start = performance.now();
const tick = (now: number) => {
progress.value = ((now - start) / 2400) % 1;
frame = requestAnimationFrame(tick);
};
frame = requestAnimationFrame(tick);
});
onBeforeUnmount(() => cancelAnimationFrame(frame));
</script>Colors
Give a connector a color, a FluxColor or any CSS color, to categorize it.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="dispatch" :x="360" :y="0">
<FluxFlowCard title="Dispatch"/>
</FluxFlowNode>
<FluxFlowNode id="email" :x="0" :y="240">
<FluxFlowCard title="Email"/>
</FluxFlowNode>
<FluxFlowNode id="sms" :x="360" :y="240">
<FluxFlowCard title="SMS"/>
</FluxFlowNode>
<FluxFlowNode id="push" :x="720" :y="240">
<FluxFlowCard title="Push"/>
</FluxFlowNode>
<FluxFlowConnection from="dispatch" to="email" from-side="bottom" to-side="top" color="info" label="Email"/>
<FluxFlowConnection from="dispatch" to="sms" from-side="bottom" to-side="top" color="success" label="SMS"/>
<FluxFlowConnection from="dispatch" to="push" from-side="bottom" to-side="top" color="danger" label="Push"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>Explicit sides
Override from-side and to-side to route a connector out of a specific edge, for example the two branches of a condition.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="check" :x="300" :y="90">
<FluxFlowConditionCard title="Is Valid?"/>
</FluxFlowNode>
<FluxFlowNode id="process" :x="720" :y="90">
<FluxFlowActionCard title="Process"/>
</FluxFlowNode>
<FluxFlowNode id="reject" :x="300" :y="240">
<FluxFlowActionCard title="Reject" icon="stopwatch" color="danger"/>
</FluxFlowNode>
<FluxFlowConnection from="check" to="process" from-side="right" to-side="left" label="Yes"/>
<FluxFlowConnection from="check" to="reject" from-side="bottom" to-side="top" label="No"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowActionCard, FluxFlowConditionCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>Loop
Point from and to at the same node and the connector loops out of one side and back into it. It uses the side a connector running against the flow would take, so a retry and a step back read alike; from-side moves it elsewhere.
<template>
<FluxFlow :padding="21" axis="vertical">
<FluxFlowNode id="fetch" :x="0" :y="0">
<FluxFlowActionCard title="Fetch the invoice" icon="server"/>
</FluxFlowNode>
<FluxFlowNode id="done" :x="0" :y="216">
<FluxFlowTerminal color="success" icon="circle-check" label="Delivered"/>
</FluxFlowNode>
<FluxFlowConnection from="fetch" to="fetch" color="warning" label="Retry" dashed/>
<FluxFlowConnection from="fetch" to="done"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowActionCard, FluxFlowConnection, FluxFlowNode, FluxFlowTerminal } from '@flux-ui/flow';
</script>Loop between two sides
Name both from-side and to-side and the loop travels from the one to the other: two adjacent sides turn a corner, two opposite ones wrap the node along the axis they do not use.
<template>
<FluxFlow :padding="45" axis="vertical">
<FluxFlowNode id="corner" :x="0" :y="0">
<FluxFlowActionCard title="Turns a corner" icon="rotate"/>
</FluxFlowNode>
<FluxFlowNode id="around" :x="0" :y="300">
<FluxFlowActionCard title="Wraps the node" icon="rotate"/>
</FluxFlowNode>
<FluxFlowConnection from="corner" to="corner" from-side="right" to-side="top" color="info" label="Corner"/>
<FluxFlowConnection from="around" to="around" from-side="right" to-side="left" color="warning" label="Around"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowActionCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>Alignment
A side is not one point: from-align and to-align move a connector to the start or end of the side it uses, which pulls two branches out of the same edge apart. start lands on the node's own icon, wherever that sits, so a card and a pill each anchor on their own; end mirrors that margin to the far corner. A node too narrow for the margin keeps its connector centered.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="check" :x="330" :y="90">
<FluxFlowConditionCard title="Is Valid?"/>
</FluxFlowNode>
<FluxFlowNode id="approve" :x="92" :y="270">
<FluxFlowActionCard title="Approve"/>
</FluxFlowNode>
<FluxFlowNode id="reject" :x="568" :y="270">
<FluxFlowActionCard title="Reject" icon="stopwatch" color="danger"/>
</FluxFlowNode>
<FluxFlowConnection from="check" to="approve" from-side="bottom" from-align="start" to-side="top" to-align="end" label="Yes"/>
<FluxFlowConnection from="check" to="reject" from-side="bottom" from-align="end" to-side="top" to-align="start" label="No"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowActionCard, FluxFlowConditionCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>Label placement
A badge rides the middle of the connector. On a connector that bends that middle can land on a bend, or on the leg two branches share, so label-placement moves it to the leg leaving the source or the leg entering the target instead.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="check" :x="0" :y="0">
<FluxFlowConditionCard title="Is Valid?"/>
</FluxFlowNode>
<FluxFlowNode id="process" :x="450" :y="48">
<FluxFlowPill icon="play" label="Process"/>
</FluxFlowNode>
<FluxFlowNode id="review" :x="450" :y="168">
<FluxFlowPill color="warning" icon="user-check" label="Review"/>
</FluxFlowNode>
<FluxFlowNode id="reject" :x="450" :y="288">
<FluxFlowPill color="danger" icon="stopwatch" label="Reject"/>
</FluxFlowNode>
<FluxFlowConnection from="check" to="process" from-side="right" to-side="left" label="first-leg" label-placement="first-leg"/>
<FluxFlowConnection from="check" to="review" from-side="right" to-side="left" label="center"/>
<FluxFlowConnection from="check" to="reject" from-side="right" to-side="left" label="last-leg" label-placement="last-leg"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowConditionCard, FluxFlowConnection, FluxFlowNode, FluxFlowPill } from '@flux-ui/flow';
</script>Icons
An icon next to a label rides along in the badge. An icon on its own drops the badge and stands on the line itself, which keeps a connector that only needs a symbol light. Both break the line the same way, and both follow the connector's color.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="intake" :x="0" :y="0">
<FluxFlowPill icon="envelope" label="Intake"/>
</FluxFlowNode>
<FluxFlowNode id="scan" :x="240" :y="0">
<FluxFlowPill icon="magnifying-glass" label="Scan"/>
</FluxFlowNode>
<FluxFlowNode id="approve" :x="480" :y="0">
<FluxFlowPill color="success" icon="user-check" label="Approve"/>
</FluxFlowNode>
<FluxFlowNode id="archive" :x="750" :y="0">
<FluxFlowPill color="warning" icon="box" label="Archive"/>
</FluxFlowNode>
<FluxFlowConnection from="intake" to="scan" icon="bolt"/>
<FluxFlowConnection from="scan" to="approve" icon="hourglass-clock" label="2 days"/>
<FluxFlowConnection from="approve" to="archive" color="warning" icon="lock"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowConnection, FluxFlowNode, FluxFlowPill } from '@flux-ui/flow';
</script>Line styles
Connectors are solid by default; set dashed or dotted for other styles. A connector leaves its source with a port dot and reaches its target with an arrow head; marker-start and marker-end swap either for the other shape, or drop it entirely.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="solid-top" :x="0" :y="0">
<FluxFlowCard title="Solid"/>
</FluxFlowNode>
<FluxFlowNode id="solid-bottom" :x="0" :y="180">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="dashed-top" :x="330" :y="0">
<FluxFlowCard title="Dashed"/>
</FluxFlowNode>
<FluxFlowNode id="dashed-bottom" :x="330" :y="180">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="dotted-top" :x="660" :y="0">
<FluxFlowCard title="Dotted"/>
</FluxFlowNode>
<FluxFlowNode id="dotted-bottom" :x="660" :y="180">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowConnection from="solid-top" to="solid-bottom"/>
<FluxFlowConnection from="dashed-top" to="dashed-bottom" dashed/>
<FluxFlowConnection from="dotted-top" to="dotted-bottom" dotted/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>Animated
Add animated to a dashed or dotted connector to travel its pattern towards the target, which reads as work in flight. It respects reduced motion.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="dashed-top" :x="0" :y="0">
<FluxFlowCard title="Dashed"/>
</FluxFlowNode>
<FluxFlowNode id="dashed-bottom" :x="0" :y="210">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="dotted-top" :x="360" :y="0">
<FluxFlowCard title="Dotted"/>
</FluxFlowNode>
<FluxFlowNode id="dotted-bottom" :x="360" :y="210">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowConnection animated dashed from="dashed-top" to="dashed-bottom" color="primary"/>
<FluxFlowConnection animated dotted from="dotted-top" to="dotted-bottom" color="info"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>Markers
Both ends carry their own marker. Give a two-way connector a head on each end, or strip them both for a bare line.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="default-top" :x="0" :y="0">
<FluxFlowCard title="Default"/>
</FluxFlowNode>
<FluxFlowNode id="default-bottom" :x="0" :y="180">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="both-top" :x="330" :y="0">
<FluxFlowCard title="Two-way"/>
</FluxFlowNode>
<FluxFlowNode id="both-bottom" :x="330" :y="180">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="bare-top" :x="660" :y="0">
<FluxFlowCard title="Bare"/>
</FluxFlowNode>
<FluxFlowNode id="bare-bottom" :x="660" :y="180">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowConnection from="default-top" to="default-bottom"/>
<FluxFlowConnection from="both-top" to="both-bottom" marker-start="chevron"/>
<FluxFlowConnection from="bare-top" to="bare-bottom" marker-start="none" marker-end="none"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>Marker shapes
Six shapes to end a connector with. chevron and arrow point at the target; dot, diamond and square read as ports; bar caps the line off.
<template>
<FluxFlow :padding="21">
<template
v-for="(marker, index) of markers"
:key="marker">
<!-- Both ends sit in a column of the same width, so a step naming its
marker centres over the port below it and the connector runs
straight down. -->
<FluxFlowNode :id="`${marker}-from`" :x="column(index)" :y="row(index)">
<div style="display: flex; width: 120px; justify-content: center">
<FluxFlowStep :value="marker"/>
</div>
</FluxFlowNode>
<FluxFlowNode :id="`${marker}-to`" :x="column(index)" :y="row(index) + 150">
<div style="display: flex; width: 120px; justify-content: center">
<FluxFlowStep :value="''"/>
</div>
</FluxFlowNode>
<FluxFlowConnection
:from="`${marker}-from`"
:to="`${marker}-to`"
marker-start="none"
:marker-end="marker"/>
</template>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowConnection, FluxFlowNode, FluxFlowStep } from '@flux-ui/flow';
const markers = ['chevron', 'arrow', 'dot', 'diamond', 'square', 'bar'] as const;
const COLUMNS = 3;
function column(index: number): number {
return (index % COLUMNS) * 180;
}
function row(index: number): number {
return Math.floor(index / COLUMNS) * 270;
}
</script>Waypoints
A connector routes itself, which is what you want until it runs straight over the card between its two ends. Give it waypoints and it runs through the points you name instead. They are plain flow coordinates, the same space as the x / y of a node, so a route is written where you can read it off the canvas.
Every shape honours them: straight becomes a polyline, smoothstep rounds each corner the way it rounds its own, step turns those same corners sharply, and bezier becomes a single smooth curve through the points. The label rides the middle of the whole route, and both endpoints still leave and reach their node along the side they use.
Three shapes, one detour
The same two waypoints in all three shapes. The endpoints are unaffected: waypoints only shape the run between them, and the label rides the middle of that run.
<template>
<FluxFlow :padding="21">
<FluxFlowNode id="smooth-top" :x="0" :y="0">
<FluxFlowCard title="Smoothstep"/>
</FluxFlowNode>
<FluxFlowNode id="smooth-bottom" :x="0" :y="330">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="bezier-top" :x="420" :y="0">
<FluxFlowCard title="Bezier"/>
</FluxFlowNode>
<FluxFlowNode id="bezier-bottom" :x="420" :y="330">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="step-top" :x="840" :y="0">
<FluxFlowCard title="Step"/>
</FluxFlowNode>
<FluxFlowNode id="step-bottom" :x="840" :y="330">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowNode id="straight-top" :x="1260" :y="0">
<FluxFlowCard title="Straight"/>
</FluxFlowNode>
<FluxFlowNode id="straight-bottom" :x="1260" :y="330">
<FluxFlowCard title="Target"/>
</FluxFlowNode>
<FluxFlowConnection from="smooth-top" to="smooth-bottom" type="smoothstep" :waypoints="[{x: 45, y: 165}, {x: 45, y: 255}]" label="Detour"/>
<FluxFlowConnection from="bezier-top" to="bezier-bottom" type="bezier" :waypoints="[{x: 465, y: 165}, {x: 465, y: 255}]" label="Detour"/>
<FluxFlowConnection from="step-top" to="step-bottom" type="step" :waypoints="[{x: 885, y: 165}, {x: 885, y: 255}]" label="Detour"/>
<FluxFlowConnection from="straight-top" to="straight-bottom" type="straight" :waypoints="[{x: 1305, y: 165}, {x: 1305, y: 255}]" label="Detour"/>
</FluxFlow>
</template>
<script
setup
lang="ts">
import { FluxFlow, FluxFlowCard, FluxFlowConnection, FluxFlowNode } from '@flux-ui/flow';
</script>TIP
The canvas sizes itself to the nodes it holds, not to the connectors. Keep waypoints inside that area, or give the Flow enough padding to cover the detour.