Node-based UIs for React, reimagined.
The fastest open source library for building flow editors, workflow builders, data pipelines and node graphs with React — with the features other libraries put behind a paywall built in and free. All 16 React Flow Pro examples now have a working, MIT-free RealFlow equivalent, each backed by a test and a live render — the head-to-head is in GAPS.md.
Quick start · Why RealFlow · Features · Docs · Live demo
React Flow (xyflow) made node-based UIs mainstream. RealFlow starts where it stops — every row in this table is a deliberate design decision, not an add-on:
| RealFlow | React Flow (xyflow) | |
|---|---|---|
| Undo / redo | ✅ Built in, transactional, drag-coalescing | 💰 Pro example, DIY |
| Auto-layout | ✅ Built in: layered, tree, force, radial, grid — zero deps | 💰 Pro example + dagre/elkjs |
| Alignment guides + snapping | ✅ Built in, Figma-style | 💰 Pro example ("helper lines") |
| Viewport culling | ✅ On by default, spatial-index backed, hysteresis | |
| Re-render on drag | ✅ Only the dragged node + its edges | |
| Pan / zoom | ✅ Direct DOM transform — zero React renders | |
| MiniMap | ✅ Canvas (no per-node React elements) | |
| Typed ports | ✅ dataType + maxConnections on handles, cycle prevention |
isValidConnection callback |
| AI-agent integration | ✅ JSON operations + validated executor, LLM tool schema, graph→Mermaid | ❌ DIY |
| Orthogonal routing w/ obstacle avoidance | ✅ Edges route around nodes (A*), re-route live | ❌ Edges cross nodes |
| Real-time collaboration | ✅ Transport-agnostic CRDT-style sync + presence, Yjs-ready | ❌ DIY |
| Migration path | ✅ @realflow/compat — drop-in React Flow API adapter |
— |
| Copy / paste / duplicate | ✅ Built in (⌘C/V/D/X), id-remapped, one undo | |
| NodeResizer / NodeToolbar / edge reconnect | ✅ Built in | ✅ (some Pro) |
| Accessibility | ✅ Focusable nodes, aria, spatial keyboard nav | |
| Graph algorithms | ✅ Topo sort, cycle detect, components, shortest path, ancestors | getIncomers / getOutgoers |
| State management | ✅ useRealFlow() — no reducers, no change handlers |
onNodesChange + applyNodeChanges boilerplate |
| Headless core | ✅ @realflow/core — zero dependencies, runs anywhere |
@xyflow/system (depends on d3-zoom/d3-drag) |
| Default look | ✅ Polished theme, dark mode, animations out of the box | |
| License | ✅ MIT, everything free | MIT + paid Pro examples |
Head-to-head benchmark vs React Flow (reproducible: npm run bench,
production builds, identical scenes, Chromium software rendering, every pan
verified to actually move the viewport — see BENCHMARKS.md):
| 10,000 nodes, zoomed-in editing | Pan FPS | DOM nodes | Heap |
|---|---|---|---|
| RealFlow | 43 | 143 | 18 MB |
| React Flow (default) | 4 | 10,000 | 239 MB |
React Flow (onlyRenderVisibleElements) |
9 | 49 | 34 MB |
RealFlow culls off-screen nodes by default via a spatial hash index, so a
10k-node graph stays interactive while using ~13× less memory than React
Flow's default (culling off). Against React Flow's own
onlyRenderVisibleElements, memory is close — RealFlow's durable edge there is
FPS (zero-render pan), not memory. When every node is genuinely on-screen
(zoomed all the way out), both libraries are paint-bound and roughly tied —
RealFlow at about half the memory. Numbers are honest and reproducible, not
marketing (full audit: AUDIT.md).
npm install @realflow/reactimport { RealFlow, Background, Controls, MiniMap } from '@realflow/react';
import '@realflow/react/styles.css';
const nodes = [
{ id: 'a', position: { x: 0, y: 0 }, data: { label: 'Hello' } },
{ id: 'b', position: { x: 260, y: 80 }, data: { label: 'World', description: 'it just works' } },
];
const edges = [{ id: 'e1', source: 'a', target: 'b', animated: true }];
export default function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<RealFlow defaultNodes={nodes} defaultEdges={edges}>
<Background />
<Controls />
<MiniMap />
</RealFlow>
</div>
);
}That's the whole app. Pan, zoom, drag, connect, box-select, delete,
undo/redo, alignment guides, dark mode — all already working.
No onNodesChange, no applyNodeChanges, no state wiring.
import { useRealFlow } from '@realflow/react';
function Toolbar() {
const flow = useRealFlow();
return (
<>
<button onClick={() => flow.addNode({ id: crypto.randomUUID(), position: { x: 0, y: 0 }, data: { label: 'New' } })}>
Add
</button>
<button onClick={() => flow.layout('layered', { duration: 300 })}>Auto layout</button>
<button onClick={() => flow.undo()}>Undo</button>
<button onClick={() => flow.fitView({ duration: 300 })}>Fit</button>
</>
);
}- Fine-grained reactivity — every node and edge subscribes to its own
topic (
node:<id>,edge:<id>). Dragging one node re-renders one node and its edges. Nothing else. - Zero-render pan/zoom — the viewport transform is written straight to the DOM. React is not involved in a single pan frame.
- Spatial hash culling — only visible nodes are mounted, with overscan hysteresis so panning doesn't churn mounts every frame.
- Batched measurement — one shared
ResizeObserver, and handle positions are measured in a single read-then-write pass (no layout thrashing when 500 nodes mount at once). - Canvas MiniMap — repaints 10k nodes in about a millisecond.
Light and dark themes with a modern look — soft shadows, hover elevation,
selection rings, animated edges — all themeable with CSS variables
(--rf-accent, --rf-node-bg, …). colorMode="auto" follows the OS.
flow.layout('layered', { direction: 'LR' }); // Sugiyama-style, handles cycles
flow.layout('tree'); // tidy trees & forests
flow.layout('force', { linkDistance: 180 }); // deterministic (seeded) FR
flow.layout('radial'); // BFS rings
flow.layout('grid', { columns: 8 });Every layout is a single undoable transaction and knows about node sizes, subflows and cycles.
Every mutation is recorded with its inverse. Drags coalesce into one entry.
Group anything with flow.transact('label', () => { ... }). ⌘Z / ⌘⇧Z
work out of the box, and useHistory() gives you reactive
canUndo/canRedo for your own UI.
<Handle kind="source" side="right" dataType="tensor" maxConnections={1} />Incompatible types can't connect. Full handles are rejected. Set
preventCycles and edges that would create a loop are refused — validated
live while dragging, with the connection line turning red.
Drag a node near another's edge or center: guide lines appear and the node
snaps. On by default (alignmentGuides={false} to disable), plus optional
snapGrid.
function MetricNode({ data, selected }: NodeProps<{ kpi: string }>) {
return (
<div className={selected ? 'ring' : ''}>
<Handle kind="target" side="left" />
{data.kpi}
<Handle kind="source" side="right" />
</div>
);
}
<RealFlow nodeTypes={{ metric: MetricNode }} … />Handles are measured automatically — put them anywhere in your markup and
edges anchor exactly. Custom edges get precomputed geometry
(path, labelX/Y, endpoints) as props.
An LLM can drive the canvas through a validated JSON operation format — with a ready-made tool schema and prompt fragment:
import { applyOperations, operationSchema, OPERATIONS_PROMPT, describeGraph, toMermaid } from '@realflow/core';
// agent emits ops via tool-calling…
applyOperations(flow.store, [
{ op: 'add_node', id: 'retry', label: 'Retry (3x)' },
{ op: 'connect', source: 'fetch', target: 'retry' },
{ op: 'set_status', id: 'fetch', status: 'running', message: 'batch 4/12' },
]); // never throws — errors are collected; one batch = one ⌘Z
describeGraph(store); // compact JSON for the model's context
toMermaid(store); // or Mermaid — the cheapest tokens you'll spendAuto-layout places position-less nodes, set_status animates live
execution, and the same zero-dependency engine validates agent output
server-side before it reaches a client. It's provider-agnostic:
examples/ai-agent drives the canvas from GLM,
Gemini or Anthropic (whichever key you set) with one transactional undo per
turn. See the AI copilot demo tab and
docs/ai-integration.md.
parentId nests nodes; children move with their parent for free (one
transform, not N re-renders). extent: 'parent' clamps children inside.
Deleting a group re-parents children instead of orphaning them.
import { topologicalSort, hasCycle, connectedComponents, shortestPath,
getAncestors, getDescendants, getIncomers, getOutgoers } from '@realflow/core';@realflow/core is headless and dependency-free — use it in Node.js for
server-side validation, tests, or CLI tooling with the exact engine the UI
runs.
Controlled or uncontrolled modes · box selection · keyboard shortcuts
(delete, select-all, arrow-nudge) · edge labels & markers · animated edges ·
bezier / smoothstep / step / straight paths · MiniMap
drag-to-navigate · fitView/centerNode with smooth animation ·
save/restore snapshots · SSR-safe · touch support with two-finger pinch
zoom · panOnScroll trackpad mode (Figma-style) · level-of-detail
rendering when zoomed out · works with Tailwind, shadcn/ui, Radix, Base UI,
MUI (integration guide).
@realflow/compat is a drop-in adapter — change your imports and an existing
React Flow app runs on RealFlow's engine (undo/redo and culling included, free):
- import { ReactFlow, Handle, Position, useReactFlow } from '@xyflow/react';
+ import { ReactFlow, Handle, Position, useReactFlow } from '@realflow/compat';useNodesState, onNodesChange/applyNodeChanges, onConnect, custom
nodeTypes, <Handle type position> all keep working. See
docs/migration.md.
| Package | What it is |
|---|---|
@realflow/core |
Headless engine: store, spatial index, paths, layouts, history, algorithms, AI ops. Zero dependencies. |
@realflow/react |
React renderer: <RealFlow>, components, hooks, theme. Depends only on core + React. |
@realflow/compat |
React Flow (xyflow) API compatibility adapter for migrations. |
Every performance claim here is backed by a reproducible benchmark, and every feature by a passing test. Three docs keep it honest: CLAIMS.md audits this README line-by-line against the code; AUDIT.md is the adversarial audit (methodology, reproduced benchmark, claim-by-claim KEEP/FIX verdicts); GAPS.md is the head-to-head against all 16 React Flow Pro examples and names plainly the ones React Flow still wins. The UI-frameworks demo is built on the real shadcn/ui (Radix) and Base UI packages, and lint, cross-browser E2E and visual-regression snapshots all run in CI.
git clone https://github.com/olbboy/realflow && cd realflow
npm install
npm run dev # showcase + 1k/5k/10k stress scenes at http://localhost:5173npm run build # build core + react (compat: npm run build -w @realflow/compat)
npm run lint # ESLint (typescript-eslint + react-hooks)
npm test # 144 unit/integration tests (vitest)
npm run typecheck # strict TS across packages
npm run test:e2e # cross-browser E2E: Chromium/Firefox/WebKit + touch (Playwright)
npm run test:e2e:visual # visual-regression snapshots (Chromium, pinned baselines)
npm run bench # reproducible head-to-head benchmark vs React Flow- Getting started
- Custom nodes & edges
- Auto-layout: built-in, incremental, off-thread
- Real-time collaboration (+ Yjs)
- AI agent integration
- Migrating from React Flow
- Tailwind / shadcn / Radix / Base UI
- Performance guide
- Core concepts & API
Interactive examples: npm run dev:docs — a live gallery (basic flow,
custom nodes, auto-layout, smart routing, AI ops, collaboration) with source
shown side-by-side.
Contributions are welcome — bug fixes, features, docs, benchmarks and honest critiques all count. Start with CONTRIBUTING.md for the dev setup, project layout, coding conventions and PR checklist. Every change is expected to keep the honesty docs true: a new feature ships with a test, a performance claim ships with a reproducible benchmark.
- 🐛 Found a bug? Open an issue.
- 💡 Have an idea? Start a discussion or a feature request.
- 🔒 Security issue? Please report privately — see SECURITY.md.
- 🤝 Be kind. This project follows the Contributor Covenant.
MIT — every feature above is free, forever. Copyright © 2026 RealFlow contributors.