React Flow (@xyflow/react) for workflow visualization with custom nodes and edges. Use when building graph visualizations, creating custom workflow nodes, implementing edge labels, or controlling viewport. Triggers on ReactFlow, @xyflow/react, Handle, NodeProps, EdgeProps, useReactFlow, fitView.
/plugin marketplace add anderskev/beagle/plugin install anderskev-beagle@anderskev/beagleThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/custom-edges.mdreferences/custom-nodes.mdreferences/events.mdreferences/viewport.mdReact Flow (@xyflow/react) is a library for building node-based graphs, workflow editors, and interactive diagrams. It provides a highly customizable framework for creating visual programming interfaces, process flows, and network visualizations.
pnpm add @xyflow/react
import { ReactFlow, Node, Edge, Background, Controls, MiniMap } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const initialNodes: Node[] = [
{
id: '1',
type: 'input',
data: { label: 'Input Node' },
position: { x: 250, y: 5 },
},
{
id: '2',
data: { label: 'Default Node' },
position: { x: 100, y: 100 },
},
{
id: '3',
type: 'output',
data: { label: 'Output Node' },
position: { x: 400, y: 100 },
},
];
const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e2-3', source: '2', target: '3' },
];
function Flow() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow nodes={initialNodes} edges={initialEdges}>
<Background />
<Controls />
<MiniMap />
</ReactFlow>
</div>
);
}
export default Flow;
Nodes are the building blocks of the graph. Each node has:
id: Unique identifiertype: Node type (built-in or custom)position: { x, y } coordinatesdata: Custom data objectimport { Node } from '@xyflow/react';
const node: Node = {
id: 'node-1',
type: 'default',
position: { x: 100, y: 100 },
data: { label: 'Node Label' },
style: { background: '#D6D5E6' },
className: 'custom-node',
};
Built-in node types:
default: Standard nodeinput: No target handlesoutput: No source handlesgroup: Container for other nodesEdges connect nodes. Each edge requires:
id: Unique identifiersource: Source node IDtarget: Target node IDimport { Edge } from '@xyflow/react';
const edge: Edge = {
id: 'e1-2',
source: '1',
target: '2',
type: 'smoothstep',
animated: true,
label: 'Edge Label',
style: { stroke: '#fff', strokeWidth: 2 },
};
Built-in edge types:
default: Bezier curvestraight: Straight linestep: Orthogonal with sharp cornerssmoothstep: Orthogonal with rounded cornersHandles are connection points on nodes. Use Position enum for placement:
import { Handle, Position } from '@xyflow/react';
<Handle type="target" position={Position.Top} />
<Handle type="source" position={Position.Bottom} />
Available positions: Position.Top, Position.Right, Position.Bottom, Position.Left
Use state hooks for full control:
import { useNodesState, useEdgesState, addEdge, OnConnect } from '@xyflow/react';
import { useCallback } from 'react';
function ControlledFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect: OnConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
/>
);
}
Access the React Flow instance for programmatic control:
import { useReactFlow } from '@xyflow/react';
function FlowControls() {
const {
getNodes,
getEdges,
setNodes,
setEdges,
addNodes,
addEdges,
deleteElements,
fitView,
zoomIn,
zoomOut,
getNode,
getEdge,
updateNode,
updateEdge,
} = useReactFlow();
return (
<button onClick={() => fitView()}>Fit View</button>
);
}
Define custom nodes using NodeProps<T> with typed data:
import { NodeProps, Node, Handle, Position } from '@xyflow/react';
export type CustomNode = Node<{ label: string; status: 'active' | 'inactive' }, 'custom'>;
function CustomNodeComponent({ data, selected }: NodeProps<CustomNode>) {
return (
<div className={`px-4 py-2 ${selected ? 'ring-2' : ''}`}>
<Handle type="target" position={Position.Top} />
<div className="font-bold">{data.label}</div>
<Handle type="source" position={Position.Bottom} />
</div>
);
}
Register with nodeTypes:
const nodeTypes: NodeTypes = { custom: CustomNodeComponent };
<ReactFlow nodeTypes={nodeTypes} />
id prop and style for positioninguseUpdateNodeInternals([nodeId]) after adding/removing handlesclassName="nodrag" to prevent dragging on inputs/buttonsSee Custom Nodes Reference for detailed patterns including styling, aviation map pins, and dynamic handles.
Define custom edges using EdgeProps<T> and path utilities:
import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react';
export type CustomEdge = Edge<{ status: 'normal' | 'error' }, 'custom'>;
function CustomEdgeComponent(props: EdgeProps<CustomEdge>) {
const [edgePath] = getBezierPath(props);
return (
<BaseEdge
id={props.id}
path={edgePath}
style={{ stroke: props.data?.status === 'error' ? '#ef4444' : '#64748b' }}
/>
);
}
getBezierPath() - Smooth curvesgetStraightPath() - Straight linesgetSmoothStepPath() - Orthogonal with rounded cornersgetSmoothStepPath({ borderRadius: 0 }) - Orthogonal with sharp corners (step edge)All return [path, labelX, labelY, offsetX, offsetY].
Use EdgeLabelRenderer for HTML-based labels with pointer events:
import { EdgeLabelRenderer, BaseEdge, getBezierPath } from '@xyflow/react';
function ButtonEdge(props: EdgeProps) {
const [edgePath, labelX, labelY] = getBezierPath(props);
return (
<>
<BaseEdge id={props.id} path={edgePath} />
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
pointerEvents: 'all',
}}
className="nodrag nopan"
>
<button onClick={() => console.log('Delete')}>×</button>
</div>
</EdgeLabelRenderer>
</>
);
}
See Custom Edges Reference for animated edges, time labels, and SVG text patterns.
Use useReactFlow() hook for programmatic viewport control:
import { useReactFlow } from '@xyflow/react';
function ViewportControls() {
const { fitView, zoomIn, zoomOut, setCenter, screenToFlowPosition } = useReactFlow();
// Fit all nodes in view
const handleFitView = () => fitView({ padding: 0.2, duration: 400 });
// Zoom controls
const handleZoomIn = () => zoomIn({ duration: 300 });
const handleZoomOut = () => zoomOut({ duration: 300 });
// Center on specific coordinates
const handleCenter = () => setCenter(250, 250, { zoom: 1.5, duration: 500 });
// Convert screen coordinates to flow coordinates
const addNodeAtClick = (event: React.MouseEvent) => {
const position = screenToFlowPosition({ x: event.clientX, y: event.clientY });
// Use position to add node
};
return null;
}
See Viewport Reference for save/restore state, controlled viewport, and coordinate transformations.
React Flow provides comprehensive event handling:
import { NodeMouseHandler, OnNodeDrag } from '@xyflow/react';
const onNodeClick: NodeMouseHandler = (event, node) => {
console.log('Node clicked:', node.id);
};
const onNodeDrag: OnNodeDrag = (event, node, nodes) => {
console.log('Dragging:', node.id);
};
<ReactFlow
onNodeClick={onNodeClick}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeClick}
/>
import { EdgeMouseHandler, OnConnect } from '@xyflow/react';
const onEdgeClick: EdgeMouseHandler = (event, edge) => console.log('Edge:', edge.id);
const onConnect: OnConnect = (connection) => console.log('Connected:', connection);
<ReactFlow onEdgeClick={onEdgeClick} onConnect={onConnect} />
import { useOnSelectionChange, useOnViewportChange } from '@xyflow/react';
useOnSelectionChange({
onChange: ({ nodes, edges }) => console.log('Selected:', nodes.length, edges.length),
});
useOnViewportChange({
onChange: (viewport) => console.log('Viewport:', viewport.zoom),
});
See Events Reference for complete event catalog including validation, deletion, and error handling.
<input className="nodrag" />
<button className="nodrag nopan">Click me</button>
const isValidConnection = (connection: Connection) => {
return connection.source !== connection.target; // Prevent self-connections
};
<ReactFlow isValidConnection={isValidConnection} />
const { screenToFlowPosition, setNodes } = useReactFlow();
const onPaneClick = (event: React.MouseEvent) => {
const position = screenToFlowPosition({ x: event.clientX, y: event.clientY });
setNodes(nodes => [...nodes, { id: `node-${Date.now()}`, position, data: { label: 'New' } }]);
};
const { updateNodeData } = useReactFlow();
updateNodeData('node-1', { label: 'Updated' });
updateNodeData('node-1', (node) => ({ ...node.data, count: node.data.count + 1 }));
Wrap the app with ReactFlowProvider when using useReactFlow() outside the flow:
import { ReactFlow, ReactFlowProvider, useReactFlow } from '@xyflow/react';
function Controls() {
const { fitView } = useReactFlow(); // Must be inside provider
return <button onClick={() => fitView()}>Fit View</button>;
}
function App() {
return (
<ReactFlowProvider>
<Controls />
<ReactFlow nodes={nodes} edges={edges} />
</ReactFlowProvider>
);
}
For detailed implementation patterns, see:
Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.