Visual Workflows

Build visual workflows and node-based editors with React Flow

Overview

Create interactive node-based editors, workflow builders, and visual diagrams using React Flow. Perfect for building no-code tools, data pipelines, and process automation interfaces.

Installation

pnpm add @xyflow/react

Basic Example

"use client"

import { useCallback } from "react"
import {
  addEdge,
  Background,
  Controls,
  MiniMap,
  ReactFlow,
  useEdgesState,
  useNodesState,
} from "@xyflow/react"

import "@xyflow/react/dist/style.css"

const initialNodes = [
  { id: "1", position: { x: 0, y: 0 }, data: { label: "Start" } },
  { id: "2", position: { x: 0, y: 100 }, data: { label: "Process" } },
  { id: "3", position: { x: 0, y: 200 }, data: { label: "End" } },
]

const initialEdges = [
  { id: "e1-2", source: "1", target: "2" },
  { id: "e2-3", source: "2", target: "3" },
]

export function WorkflowBuilder() {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)

  const onConnect = useCallback(
    (params) => setEdges((eds) => addEdge(params, eds)),
    [setEdges]
  )

  return (
    <div className="h-[600px] w-full">
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
      >
        <Controls />
        <MiniMap />
        <Background variant="dots" gap={12} size={1} />
      </ReactFlow>
    </div>
  )
}

Custom Nodes

Create custom node types that match your UI design:

import { Handle, Position } from '@xyflow/react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'

function CustomNode({ data }) {
  return (
    <Card className="w-64">
      <CardHeader>
        <CardTitle className="text-sm">{data.label}</CardTitle>
        {data.badge && <Badge variant="outline">{data.badge}</Badge>}
      </CardHeader>
      <CardContent>
        <p className="text-xs text-muted-foreground">{data.description}</p>
      </CardContent>

      <Handle type="target" position={Position.Top} />
      <Handle type="source" position={Position.Bottom} />
    </Card>
  )
}

const nodeTypes = {
  custom: CustomNode,
}

// Use in ReactFlow
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} />

Use Cases

1. AI Agent Workflows

Build visual AI agent orchestration:

const aiNodes = [
  {
    id: "input",
    type: "custom",
    position: { x: 0, y: 0 },
    data: { label: "User Input", badge: "Start" },
  },
  {
    id: "llm",
    type: "custom",
    position: { x: 0, y: 100 },
    data: { label: "LLM Agent", description: "Claude 3.5 Sonnet" },
  },
  {
    id: "tool",
    type: "custom",
    position: { x: 200, y: 100 },
    data: { label: "Tool Use", description: "Execute functions" },
  },
  {
    id: "output",
    type: "custom",
    position: { x: 0, y: 200 },
    data: { label: "Response", badge: "End" },
  },
]

2. Data Pipeline Builder

Create ETL pipelines visually:

const pipelineNodes = [
  { id: "source", data: { label: "Data Source", type: "database" } },
  { id: "transform", data: { label: "Transform", type: "function" } },
  { id: "filter", data: { label: "Filter", type: "condition" } },
  { id: "sink", data: { label: "Data Sink", type: "storage" } },
]

3. UI Component Tree

Visualize component hierarchies:

const componentTree = [
  { id: "app", data: { label: "App", component: "Layout" } },
  { id: "header", data: { label: "Header", component: "Navigation" } },
  { id: "main", data: { label: "Main", component: "Content" } },
  { id: "footer", data: { label: "Footer", component: "Footer" } },
]

Styling with Hanzo UI

React Flow integrates seamlessly with Hanzo UI theming:

import { ReactFlow } from "@xyflow/react"

import "@xyflow/react/dist/style.css"

export function ThemedFlow() {
  return (
    <div className="h-[600px] rounded-lg border bg-background">
      <ReactFlow
        nodes={nodes}
        edges={edges}
        className="bg-background"
        style={{
          background: "hsl(var(--background))",
        }}
      >
        <Controls className="border-border bg-card" />
        <MiniMap className="border-border bg-card" nodeBorderRadius={8} />
        <Background color="hsl(var(--muted-foreground))" gap={16} />
      </ReactFlow>
    </div>
  )
}

Advanced Features

Edge Types

Create custom edge connections:

import { BaseEdge, EdgeLabelRenderer, getStraightPath } from "@xyflow/react"

import { Badge } from "@/components/ui/badge"

function CustomEdge({ id, sourceX, sourceY, targetX, targetY, data }) {
  const [edgePath, labelX, labelY] = getStraightPath({
    sourceX,
    sourceY,
    targetX,
    targetY,
  })

  return (
    <>
      <BaseEdge path={edgePath} />
      <EdgeLabelRenderer>
        <div
          style={{
            position: "absolute",
            transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
          }}
        >
          <Badge>{data.label}</Badge>
        </div>
      </EdgeLabelRenderer>
    </>
  )
}

Interactive Controls

Add custom controls using Hanzo UI components:

import { Controls } from "@xyflow/react"
import { Maximize, ZoomIn, ZoomOut } from "lucide-react"

import { Button } from "@/components/ui/button"

;<Controls>
  <Button size="icon" variant="ghost">
    <ZoomIn className="h-4 w-4" />
  </Button>
  <Button size="icon" variant="ghost">
    <ZoomOut className="h-4 w-4" />
  </Button>
  <Button size="icon" variant="ghost">
    <Maximize className="h-4 w-4" />
  </Button>
</Controls>

Persistence

Save and load workflows:

function WorkflowEditor() {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)

  const saveWorkflow = () => {
    const workflow = { nodes, edges }
    localStorage.setItem("workflow", JSON.stringify(workflow))
  }

  const loadWorkflow = () => {
    const saved = localStorage.getItem("workflow")
    if (saved) {
      const { nodes, edges } = JSON.parse(saved)
      setNodes(nodes)
      setEdges(edges)
    }
  }

  return (
    <div className="space-y-4">
      <div className="flex gap-2">
        <Button onClick={saveWorkflow}>Save</Button>
        <Button onClick={loadWorkflow} variant="outline">
          Load
        </Button>
      </div>
      <ReactFlow nodes={nodes} edges={edges} />
    </div>
  )
}

Integration with Hanzo UI Components

Node Toolbar

Add actions to nodes:

import { NodeToolbar, Position } from "@xyflow/react"
import { Copy, Settings, Trash2 } from "lucide-react"

import { Button } from "@/components/ui/button"

function NodeWithToolbar({ data }) {
  return (
    <>
      <NodeToolbar position={Position.Top}>
        <div className="flex gap-1">
          <Button size="icon" variant="ghost">
            <Settings className="h-4 w-4" />
          </Button>
          <Button size="icon" variant="ghost">
            <Copy className="h-4 w-4" />
          </Button>
          <Button size="icon" variant="ghost">
            <Trash2 className="h-4 w-4" />
          </Button>
        </div>
      </NodeToolbar>
      <Card>{/* Node content */}</Card>
    </>
  )
}

Context Menu

Right-click context menu:

import { useReactFlow } from "@xyflow/react"

import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuTrigger,
} from "@/components/ui/context-menu"

function FlowWithContextMenu() {
  const { addNodes, deleteElements } = useReactFlow()

  return (
    <ContextMenu>
      <ContextMenuTrigger>
        <ReactFlow nodes={nodes} edges={edges} />
      </ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuItem onClick={() => addNodes(newNode)}>
          Add Node
        </ContextMenuItem>
        <ContextMenuItem onClick={() => deleteElements({ nodes: [{ id }] })}>
          Delete
        </ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  )
}

Resources

dnd-kit

React Flow

See Also