Node-based editors for Blazor,
the React Flow way.
Workflow editors, pipelines, state machines and diagrams: a pannable, zoomable BlowCanvas
over your own List<BlowNode> and List<BlowEdge>, with custom
nodes, handles, five edge paths, sub-flows, a minimap, auto-layout and undo. Dragging, connecting,
keyboard handling, layout and edge geometry are written in C# - there is no JavaScript diagram library
underneath.
dotnet add package Bit.Blow
<BlowCanvas Nodes="_nodes" Edges="_edges" FitViewOnInit="true"> <BlowBackground /> <BlowControls /> </BlowCanvas> private List<BlowNode> _nodes = [ new() { Id = "1", Type = "input", Label = "Request", Position = new(0, 0) }, new() { Id = "2", Label = "Review", Position = new(-90, 110) }, new() { Id = "3", Type = "output", Label = "Approve", Position = new(0, 220) }, ];
Capabilities
Everything a node editor needs - as Blazor components
Blow follows React Flow's concepts and, wherever C# allows, its names. The graph is plain C#: the same lists can be built, validated, laid out, copied and saved with no UI at all, which is what makes a flow testable and usable on a server.
Nodes and handles
Built-in input, default, output and group
nodes, and a NodeTemplate for anything else - with BlowHandles wherever
you put them, measured in the browser so edges always meet them.
Five edge paths
Bezier, simple bezier, straight, step and smooth step, each tunable per edge, with labels, arrowheads in any colour and size, animation, and self-connections that loop clear of their node.
Edge types →Connections with rules
Drag or click from handle to handle, with proximity snapping, duplicate and self-connection rules,
IsValidConnection on the canvas or on each handle, and reconnection of existing edges.
Sub-flows and resizing
Children move with their parent and can be pinned inside it or grow it; BlowNodeResizer,
node and edge toolbars and a portal into the zoomed layer sit right next to them.
Layout without JavaScript
BlowLayout's layered (with crossing reduction), grid and circle layouts, the align and
distribute commands of a drawing tool, and alignment guides while you drag - all in C#.
Undo, clipboard and saving
HistoryEnabled keeps the undo stack and binds Ctrl/Cmd+Z; copy and paste rewire edges
and keep groups intact; ToObject() and RestoreAsync save and reload a flow.
In practice
See it in code
Install and link the stylesheet
One package and one stylesheet link - there are no services to register, and the script module is imported by the component the first time it needs it.
Getting started →dotnet add package Bit.Blow <!-- App.razor or index.html --> <link rel="stylesheet" href="_content/Bit.Blow/bit-blow.css" /> @* _Imports.razor *@ @using Bit.Blow
Render your own nodes
A NodeTemplate switches on node.Type, with BlowDefaultNode
for the rest. Handles go wherever the markup puts them; give each its own Id to tell
an edge which one it starts from.
<BlowCanvas Nodes="_nodes" Edges="_edges"> <NodeTemplate Context="node"> @if (node.Type == "color") { <BlowHandle Type="BlowHandleType.Target" Position="BlowPosition.Left" /> <div>@node.Label</div> <BlowHandle Type="BlowHandleType.Source" Id="a" Position="BlowPosition.Right" /> } else { <BlowDefaultNode Node="node" /> } </NodeTemplate> </BlowCanvas>
React to edits, drive it from code
The canvas edits your lists in place and raises NodesChanged once an edit is
complete - not on every pointer move. A @ref is the imperative API, the counterpart
of useReactFlow().
<BlowCanvas @ref="_flow" Nodes="_nodes" Edges="_edges" NodesChanged="Save" OnConnect="OnConnect" /> _flow.FitView(); await _flow.SetCenterAsync(x: 400, y: 200, zoom: 1.5, durationMs: 300); BlowSnapshot snapshot = _flow.ToObject(); // nodes, edges and viewport
Comparison
Coming from React Flow?
The same concepts under the names a Blazor component gives them. The main difference is state: there are no change objects to apply - the canvas edits your lists in place and tells you afterwards, and a connection adds its own edge.
| Concern | React Flow | Bit.Blow |
|---|---|---|
| The component | <ReactFlow> | <BlowCanvas> |
| The imperative API | useReactFlow() | a @ref to BlowCanvas |
| Applying edits | onNodesChange + applyNodeChanges | nothing to apply; NodesChanged after the edit |
| Custom nodes | nodeTypes | NodeTemplate switching on node.Type |
| Handles | <Handle type="source" position={Position.Right} /> | <BlowHandle Type="BlowHandleType.Source" Position="BlowPosition.Right" /> |
| Auto-layout | dagre or elkjs, outside the library | BlowLayout, built in |
| Undo and redo | yours to write | HistoryEnabled, or BlowHistory driven by you |
Where to start
Choose your path
New to Blow?
Install, link the stylesheet, give the canvas a height - a working editor in five minutes.
Getting started →Coming from React Flow?
Its examples are ported one for one, so the one you know is a click away - with its Blazor source.
Browse the examples →Building with an AI agent?
This site is an MCP server: point your agent at /mcp and it reads the shipped library instead of guessing.
Questions
Frequently asked
Is this React Flow running through JavaScript interop?
No. Dragging, selecting, connecting, resizing, keyboard handling, layout and edge geometry are written in C# and run on Blazor's own event system. A small script module does only what the browser alone can: measure DOM rectangles, notice when they change, find the handle under a point, hold pointer capture and keep the page from scrolling under a gesture.
Which render modes does it work in?
All of them, on .NET 8, 9 and 10. WebAssembly is the recommended model for editing - every pointer move is handled in the browser. Server works with the same features, a round trip per move; Auto and Hybrid work too. A statically rendered page shows the canvas but never becomes interactive.
Why does my canvas not show up?
The canvas fills its container, so the container needs a height - or give the canvas one with
Style. And the stylesheet is not injected for you: without the
_content/Bit.Blow/bit-blow.css link the canvas renders unstyled.
How large a graph can it handle?
Every node's position is resolved once per render and edge paths are cached, so a pan rebuilds no
path strings. With OnlyRenderVisibleElements only what intersects the view is rendered -
the stress example builds grids of up to 900 nodes.
Does it have a dark mode?
Yes: ColorMode is Light, Dark or System, and every
colour - the canvas, the control bar, the minimap, toolbars and labels - is a --bit-blow-*
custom property, so a theme is a handful of overrides. This site's flows follow its theme toggle.
Draw the first edge
One package, one stylesheet, a container with a height - and your app has a node editor.
dotnet add package Bit.Blow