Nodes
The node shape, the default rendering, and how to swap in your own body or handles.
#Node Shape
A node is a plain JavaScript object that lives in the nodes prop. It needs a unique id, a position in flow coordinates, and a data bag of whatever payload your renderer wants — a label, a status, a row from your database. Everything else (selected, dragging, measured size, absolute position) is computed by the store.
1// A node is a plain object: id, position, data.2const nodes = [3 { id: "a", position: { x: 80, y: 30 }, data: { label: "Hello" } },4 // Optional fields:5 // - type : key into the `nodeTypes` map (custom rendering)6 // - selected : initial selection state7 // - draggable : disable per-node drag8]#Default Body
Pass nodes to <Flow> with no nodeTypes map and Flow renders each node's data.label inside the built-in card — a target handle on top, a source handle on the bottom, themed border / fill via the design tokens.
#Two default nodes
1import { Flow, Background } from "@/components/ui/xyflow"23const nodes = [4 { id: "a", position: { x: 80, y: 30 }, data: { label: "Hello" } },5 { id: "b", position: { x: 280, y: 150 }, data: { label: "World" } },6]78export function MyFlow() {9 return (10 <div className="w-full h-[240px] rounded-lg border bg-background overflow-hidden">11 <Flow nodes={nodes} edges={[]}>12 <Background variant="dots" gap={30} />13 </Flow>14 </div>15 )16}#Custom Bodies
To customise the body — colour, layout, an icon, an inline status — pass a renderNode callback to <Flow>. Flow calls it once per node and forwards the live id / data / selected as props.
#Source / Pipeline / Sink
1"use client"23import { Background, Flow, Handle } from "@/components/ui/xyflow"4import { Position } from "@barefootjs/xyflow"56// Per-id colour palette so each node looks distinct from the others7// and from Flow's default body. Flow strips the default card styling8// (padding / border / background) automatically when `renderNode` is9// provided so the body below paints the entire node visual.10const tone = {11 src: "bg-emerald-500 text-white border-emerald-600",12 mid: "bg-amber-500 text-white border-amber-600",13 dst: "bg-sky-500 text-white border-sky-600",14}1516const nodes = [17 { id: "src", position: { x: 80, y: 100 }, data: { label: "Source" } },18 { id: "mid", position: { x: 320, y: 80 }, data: { label: "Pipeline" } },19 { id: "dst", position: { x: 560, y: 120 }, data: { label: "Sink" } },20]21const edges = [22 { id: "src-mid", source: "src", target: "mid" },23 { id: "mid-dst", source: "mid", target: "dst" },24]2526export function MyFlow() {27 return (28 <Flow29 nodes={nodes}30 edges={edges}31 renderNode={(n) => (32 <div className={`rounded-full border-2 px-5 py-2 text-sm font-semibold shadow-md ${tone[n.id]}`}>33 <Handle type="target" position={Position.Left} nodeId={n.id} />34 {n.data.label}35 <Handle type="source" position={Position.Right} nodeId={n.id} />36 </div>37 )}38 >39 <Background variant="dots" gap={30} />40 </Flow>41 )42}#Custom Handles
The default node has one target handle on top and one source handle on the bottom. For richer graphs you can mount as many <Handle> elements as you need, each with its own position and an id so edges can pin to a specific connection point via sourceHandle / targetHandle.
#Three-way fan-out
1// Multiple handles per node — give each one a stable `id`2// and reference it from the edge's `sourceHandle` / `targetHandle`.3const nodes = [4 { id: "fan", position: { x: 80, y: 120 }, data: { label: "Router" } },5 { id: "a", position: { x: 360, y: 30 }, data: { label: "A" } },6 { id: "b", position: { x: 360, y: 140 }, data: { label: "B" } },7 { id: "c", position: { x: 360, y: 240 }, data: { label: "C" } },8]9const edges = [10 { id: "fan-a", source: "fan", sourceHandle: "top", target: "a" },11 { id: "fan-b", source: "fan", sourceHandle: "right", target: "b" },12 { id: "fan-c", source: "fan", sourceHandle: "bottom", target: "c" },13]1415<Flow16 nodes={nodes}17 edges={edges}18 renderNode={(n) =>19 n.id === "fan" ? (20 <div className="rounded-full border-2 border-violet-600 bg-violet-500 text-white px-5 py-2 text-sm font-semibold shadow-md">21 <Handle type="source" position={Position.Top} nodeId={n.id} id="top" />22 <Handle type="source" position={Position.Right} nodeId={n.id} id="right" />23 <Handle type="source" position={Position.Bottom} nodeId={n.id} id="bottom" />24 {n.data.label}25 </div>26 ) : (27 <div className="rounded-full border-2 border-slate-400 bg-white text-slate-800 px-4 py-1.5 text-sm font-medium shadow-sm">28 <Handle type="target" position={Position.Left} nodeId={n.id} />29 {n.data.label}30 </div>31 )32 }33>34 <Background variant="dots" gap={30} />35</Flow>#Highlight Depth
A slider drives a depth() signal that's read by every node inside renderNode. Each rendered node writes its own inline style={{'--node-glow': intensity}}CSS variable so the glow can fade per-node without a chart-level re-render.
#Per-node CSS variable
1"use client"23import { createSignal } from "@barefootjs/client"4import { Flow, Handle, Background } from "@/components/ui/xyflow"5import { Position } from "@barefootjs/xyflow"67const [depth, setDepth] = createSignal(2)89const nodes = [10 { id: "root", position: { x: 80, y: 130 }, data: { label: "Root", depth: 0 } },11 { id: "l1", position: { x: 280, y: 60 }, data: { label: "Tier 1", depth: 1 } },12 // ...13]1415function HighlightDepthNode({ id }) {16 const node = nodes.find((n) => n.id === id)17 const nodeDepth = node.data.depth18 const intensity = () =>19 depth() >= nodeDepth20 ? Math.max(0, 1 - (depth() - nodeDepth) * 0.25).toFixed(2)21 : "0"22 return (23 <div24 style={{25 "--node-glow": intensity(),26 opacity: "calc(0.3 + 0.7 * var(--node-glow))",27 boxShadow: "0 0 calc(12px * var(--node-glow)) currentColor",28 }}29 >30 <Handle type="target" position={Position.Left} nodeId={id} />31 {node.data.label}32 <Handle type="source" position={Position.Right} nodeId={id} />33 </div>34 )35}3637<input type="range" min="0" max="4"38 value={String(depth())}39 onInput={(e) => setDepth(Number(e.target.value))} />40<Flow nodes={nodes} edges={edges}41 renderNode={(n) => <HighlightDepthNode id={n.id} />}>42 <Background variant="dots" gap={30} />43</Flow>