Introduction
Node graphs in BarefootJS — start with createSignal + SVG for small canvases, reach for @barefootjs/xyflow once you need pan / zoom / drag-to-connect / minimap and friends.
#Overview
BarefootJS lets you bind signals directly to SVG attributes — cx, cy, d, and viewBox all update granularly when their underlying signals change. That alone is enough to build a small interactive canvas. @barefootjs/xyflow wraps @xyflow/system (the engine behind React Flow / Svelte Flow) with a signal-friendly store and a JSX-native renderer for everything bigger.
#Simple Example
For a small canvas, plain signals plus SVG attribute bindings go a long way. The demo below keeps nodes / edges / zoom in createSignal, recomputes the edge d path whenever an endpoint moves, and reactively rewrites the root viewBox. No extra dependencies.
#SVG canvas with createSignal
1"use client"23import { createSignal, createMemo } from '@barefootjs/client'45// Nodes / edges / zoom live in plain signals. SVG attributes bind6// directly: <circle cx={n.x}/> tracks a node's position, edge <path d=7// {edgePath(e)}/> rebuilds whenever any endpoint moves, and the root8// viewBox reacts to zoom. No extra package required.910function GraphEditor() {11 const [nodes, setNodes] = createSignal(INITIAL_NODES)12 const [edges, setEdges] = createSignal(INITIAL_EDGES)13 const [zoom, setZoom] = createSignal(1)1415 const viewBox = createMemo(() => {16 const w = 720 / zoom(), h = 400 / zoom()17 return `${360 - w / 2} ${200 - h / 2} ${w} ${h}`18 })1920 return (21 <svg viewBox={viewBox()} onPointerMove={onMove} onPointerUp={onUp}>22 <g>23 {edges().map((e) => (24 <path key={e.id} d={edgePath(e)} stroke="#94a3b8" fill="none" />25 ))}26 </g>27 <g>28 {nodes().map((n) => (29 <g key={n.id} data-node-id={n.id} onPointerDown={onNodeDown}>30 <circle cx={n.x} cy={n.y} r={28} />31 <text x={n.x} y={n.y}>{n.label}</text>32 </g>33 ))}34 </g>35 </svg>36 )37}#When to Reach for @barefootjs/xyflow
The signal-bound SVG approach stays pleasant up until you start needing the things every real node-graph editor ends up needing:
- Pointer-paced pan and zoom (with momentum, bounds, and pinch support)
- Drag-to-connect handles with snapping and connection validation
- Fit-view, zoom-to-node, and a coordinate transform shared across overlays
- Selection rectangles, multi-select, and keyboard nudging
- Custom HTML node bodies that participate in edge routing and resizing
At that point, @barefootjs/xyflow packages all of it behind a small JSX-native API.
#Installation
The renderer components ship via the shadcn-style registry. The utility helpers (signal hooks, store, types, geometry helpers) live in @barefootjs/xyflow and are pulled in by the registry install.
npx @barefootjs/cli add xyflow#Quick Start
Two draggable nodes connected by an edge, on a dotted background with zoom controls. The surrounding <div> must have explicit width and height — Flow measures its container to size the canvas, so a zero-sized parent renders nothing.
1"use client"23import {4 Flow,5 Background,6 Controls,7} from "@/components/ui/xyflow"89const nodes = [10 { id: "a", position: { x: 80, y: 30 }, data: { label: "Hello" } },11 { id: "b", position: { x: 320, y: 180 }, data: { label: "World" } },12]13const edges = [14 { id: "a-b", source: "a", target: "b" },15]1617export function MyFlow() {18 return (19 <div className="w-full h-[420px] rounded-lg border bg-background overflow-hidden">20 <Flow nodes={nodes} edges={edges}>21 <Background variant="dots" gap={30} />22 <Controls />23 </Flow>24 </div>25 )26}#Next Steps
- Nodes — the node shape, custom node bodies, and per-node connection handles.
- Edges — the edge shape, path variants, animated strokes, markers, and custom edge components.
- Components — every built-in piece (
Flow,Background,Controls,MiniMap,Handle,NodeWrapper,SimpleEdge) with prop tables. - xyflow / react getting started — the upstream tutorial this section is modelled on; the engine concepts (handles, connections, viewport) carry over directly.