Get Started
Introduction
Components
Accordion
Badge
Button
Card
Checkbox
Command
Dialog
Dropdown Menu
Input
Select
Switch
Table
Tabs
Toast
Tooltip
Forms
Introduction
Validation
Field Arrays

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

Hello
World
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

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

Router
A
B
C
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

Root
Tier 1
Tier 1
Tier 2
Tier 2
Tier 2
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>