Reactor.Community.ReactorFlow 0.4.0-preview.1

This is a prerelease version of Reactor.Community.ReactorFlow.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Reactor.Community.ReactorFlow --version 0.4.0-preview.1
                    
NuGet\Install-Package Reactor.Community.ReactorFlow -Version 0.4.0-preview.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Reactor.Community.ReactorFlow" Version="0.4.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Reactor.Community.ReactorFlow" Version="0.4.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="Reactor.Community.ReactorFlow" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Reactor.Community.ReactorFlow --version 0.4.0-preview.1
                    
#r "nuget: Reactor.Community.ReactorFlow, 0.4.0-preview.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Reactor.Community.ReactorFlow@0.4.0-preview.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Reactor.Community.ReactorFlow&version=0.4.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Reactor.Community.ReactorFlow&version=0.4.0-preview.1&prerelease
                    
Install as a Cake Tool

Reactor.Community.ReactorFlow

A declarative, node-based graph / canvas library for Microsoft.UI.Reactor, inspired by React Flow. If you build node editors, flow diagrams, pipelines, mind maps or whiteboards on the web with React Flow, ReactorFlow gives you the same mental model — nodes, edges, handles, a pan/zoom viewport, and opt-in chrome — natively in WinUI.

It is standalone: it draws its own edge paths (bezier / step / smooth-step / straight) and depends only on Microsoft.UI.Reactor and Microsoft.WindowsAppSDK. Nothing is tied to any particular application.

dotnet add package Reactor.Community.ReactorFlow

Hello, flow

using Reactor.Community.ReactorFlow;
using static Microsoft.UI.Reactor.Factories;

public sealed class MyBoard : Component
{
    public override Element Render()
    {
        var (nodes, setNodes) = UseState<IReadOnlyList<ReactorFlowNode>>(new[]
        {
            new ReactorFlowNode("a", 80, 120, 160, 64, Data: "Source",
                Ports: new[] { new ReactorFlowPort("out", ReactorFlowPosition.Right) }),
            new ReactorFlowNode("b", 360, 120, 160, 64, Data: "Sink",
                Ports: new[] { new ReactorFlowPort("in", ReactorFlowPosition.Left) }),
        });

        var edges = new[] { new ReactorFlowEdge("a-b", "a", "b", "out", "in", Label: "flow") };

        return Component<ReactorFlow, ReactorFlowProps>(new ReactorFlowProps(
            Nodes: nodes,
            Edges: edges,
            OnNodesChange: changes => setNodes(ReactorFlow.ApplyNodeChanges(changes, nodes)),
            Background: ReactorFlow.Background(),
            Overlays: new[]
            {
                ReactorFlow.MiniMap(),
                ReactorFlow.Controls(),
            }));
    }
}

Concepts

ReactorFlow keeps React Flow's vocabulary so the migration is mostly mechanical:

React Flow ReactorFlow
<ReactFlow nodes edges /> Component<ReactorFlow, ReactorFlowProps>(...)
Node ReactorFlowNode (data-only record: id, position, size, type, data, ports)
Edge ReactorFlowEdge (source/target + optional ports, kind, label, animated)
Handle (Position.Left…) ReactorFlowPort on a node, or ReactorFlow.Handle(...) in custom content
nodeTypes IReadOnlyDictionary<string, Func<ReactorFlowNodeContext, Element>>
custom handle content PortTypes (Func<ReactorFlowPortContext, Element> per ReactorFlowPort.Type)
onConnect(connection) OnConnect(ReactorFlowConnection)
isValidConnection(connection) IsValidConnection(ReactorFlowConnection) => bool
onNodeClick / onEdgeClick OnNodeClick / OnEdgeClick
onNodeDoubleClick / onNodeContextMenu OnNodeDoubleClick / OnNodeContextMenu
onNodeDragStart / onNodeDragStop OnNodeDragStart / OnNodeDragStop
onEdgeDoubleClick OnEdgeDoubleClick
onPaneClick / onPaneContextMenu OnPaneClick / OnPaneContextMenu
onSelectionChange OnSelectionChange(IReadOnlyList<string> nodeIds)
onConnectStart / onConnectEnd OnConnectStart(nodeId, portId) / OnConnectEnd
onNodesChange / applyNodeChanges OnNodesChange / ReactorFlow.ApplyNodeChanges
onEdgesChange / applyEdgeChanges OnEdgesChange / ReactorFlow.ApplyEdgeChanges
getConnectedEdges(node, edges) ReactorFlow.GetConnectedEdges(nodeId, edges)
screenToFlowPosition / flowToScreenPosition ReactorFlow.ScreenToFlowPosition / FlowToScreenPosition
onReconnect / reconnectEdge OnReconnect / ReactorFlow.ReconnectEdge(edgeId, connection, edges)
toObject() / layout-persistence recipe ctx.UsePersistedFlowLayout(persisted, baseNodes, onCommit, seed?)
edgeTypes EdgeTypes (Func<ReactorFlowEdgeContext, Element> per key)
<BaseEdge /> ReactorFlow.BaseEdge(ctx, …)
<EdgeLabelRenderer /> ReactorFlow.EdgeLabelRenderer(ctx, content, offsetX?, offsetY?)
getBezierPath / getSmoothStepPath / getStraightPath ReactorFlow.GetBezierPath / GetSmoothStepPath / GetStepPath / GetStraightPath
getIntersectingNodes / isNodeIntersecting ReactorFlow.GetIntersectingNodes / IsNodeIntersecting
colorMode="light" / "dark" / "system" ReactorFlowOptions.ColorMode
MarkerType.Arrow / ArrowClosed ReactorFlowEdgeMarker on ReactorFlowEdge.MarkerStart / MarkerEnd
snapToGrid / snapGrid ReactorFlowOptions.SnapToGrid / SnapGridSize
onlyRenderVisibleElements ReactorFlowOptions.OnlyRenderVisibleElements
parentId (sub-flows) ReactorFlowNode.ParentId
<NodeResizer /> / <NodeToolbar /> ReactorFlow.NodeResizer / ReactorFlow.NodeToolbar
<Background /> ReactorFlow.Background(variant, gap, size, color)
<MiniMap pannable zoomable /> ReactorFlow.MiniMap(corner, width, margin) or ReactorFlow.MiniMap(ReactorFlowMiniMapOptions)
<Controls /> ReactorFlow.Controls(corner, showZoom, showFitView, margin)
<Panel position> ReactorFlow.Panel(corner, content, margin)
fitView() ReactorFlow.FitView(...), or the Fit button in Controls

Nodes are data; renderers are components

A node is a plain record. How it looks is decided by a node type renderer — an Element factory keyed by ReactorFlowNode.Type. Because a renderer returns any Element, a node can be a full Reactor component with its own hooks and local state:

var nodeTypes = new Dictionary<string, Func<ReactorFlowNodeContext, Element>>
{
    ["counter"] = ctx => Component<CounterNode, CounterNodeProps>(new(ctx)),
};

Everything is opt-in

There is no default chrome. A bare ReactorFlow is just nodes, edges and a pan/zoom pane. Add Background, MiniMap, Controls or arbitrary Panel content only when you want them — pass them via Background: (bottom layer) and Overlays: (corner-anchored top layer).

MiniMap interactivity

ReactorFlow.MiniMap() is a static overview by default. Pass ReactorFlowMiniMapOptions to opt into interaction and styling — every flag defaults off, so the plain call is unchanged:

ReactorFlow.MiniMap(new ReactorFlowMiniMapOptions(
    ReactorFlowCorner.BottomRight,
    Pannable: true,        // drag the map to pan the surface (React Flow's `pannable`)
    Zoomable: true,        // wheel over the map to zoom, clamped to Min/MaxZoom (`zoomable`)
    NodesDraggable: true,  // drag nodes from the map — emits the same PositionChange stream
    NodeColor: n => TintByType(n.Type),  // React Flow's `nodeColor`
    NodeScale: n => n.Type == "output" ? 1.4 : 1.0))  // enlarge emphasized nodes in the overview

Pannable recenters the surface on the point under the cursor; Zoomable zooms about the surface center within the surface's MinZoom / MaxZoom. NodesDraggable is a community extension (not in React Flow) whose drags flow through the normal OnNodesChange / ApplyNodeChanges path, so UsePersistedFlowLayout persists them too. NodeColor / NodeStrokeColor restyle node rects, NodeScale scales each rect about its center to emphasize nodes, and setting MaskColor swaps the accent viewport box for React Flow's dim off-screen mask.

Handles / ports

Attach ports to a node via ReactorFlowNode.Ports (one per side, or several with Alignment), or place ReactorFlow.Handle(position) inside custom node content. When OnConnect is supplied, dragging from a port draws a live connection and snaps to the nearest target port within ReactorFlowOptions.ConnectThreshold.

Custom port content

Give a port a Type and register a renderer under that key in ReactorFlowProps.PortTypes — the same type -> renderer shape as NodeTypes / EdgeTypes — to replace the default dot with any content (a labeled chip, a status badge, an icon). ReactorFlow draws the returned element centered on the port's anchor point for any side or alignment, so the custom content and the edge endpoint always coincide, and a pointer press still starts a drag-to-connect when OnConnect is wired. Ports without a matching renderer keep the default themed dot.

var portTypes = new Dictionary<string, Func<ReactorFlowPortContext, Element>>
{
    ["labeled"] = ctx => Border(
            TextBlock(ctx.Data?.ToString() ?? ctx.Id).Margin(8, 2))
        .CornerRadius(9),
};
// node port: new ReactorFlowPort("out", ReactorFlowPosition.Right, Type: "labeled", Data: "sum")
// props:     new ReactorFlowProps(..., PortTypes: portTypes)

Edges

Edges are drawn as native PathGeometry — no external connector library:

  • ReactorFlowEdgeKind.Default — cubic bezier
  • ReactorFlowEdgeKind.SmoothStep — orthogonal with rounded corners
  • ReactorFlowEdgeKind.Step — orthogonal, square corners
  • ReactorFlowEdgeKind.Straight — direct line

Each edge supports a Label, a Color, StrokeWidth, and Animated marching-ants. Only animated edges re-render on the animation tick.

Endpoint arrowheads are set per edge through MarkerStart / MarkerEnd (ReactorFlowEdgeMarker.None / Arrow / ArrowClosed / Dot); the default edge ends in an Arrow.

Custom edges

Register a renderer per ReactorFlowEdge.Type via EdgeTypes — the React Flow edgeTypes shape. A renderer receives a ReactorFlowEdgeContext (resolved endpoints, sides, selection, theme, and Center) and returns any Element. Draw the standard path with ReactorFlow.BaseEdge(ctx, …) (React Flow's <BaseEdge>) and add your own label or button at ctx.Center:

var edgeTypes = new Dictionary<string, Func<ReactorFlowEdgeContext, Element>>
{
    ["button"] = ctx => Canvas(
        ReactorFlow.BaseEdge(ctx),
        Button("+", () => AddNodeOnEdge(ctx.Id)).Canvas(ctx.Center.X, ctx.Center.Y)),
};

Reconnecting edges

Supply OnReconnect (and leave ReactorFlowOptions.EdgesReconnectable on) to let users drag a selected edge's endpoint onto a different port. The handler receives the edge id and the new ReactorFlowConnection; apply it with the pure ReactorFlow.ReconnectEdge(edgeId, connection, edges) helper.

Events

Beyond OnNodeClick / OnEdgeClick, ReactorFlow raises the full React Flow interaction surface: OnNodeDoubleClick, OnNodeContextMenu, OnNodeDragStart / OnNodeDragStop, OnEdgeDoubleClick, OnPaneClick / OnPaneContextMenu, OnSelectionChange (the current selected-node id list), and OnConnectStart / OnConnectEnd around a drag-to-connect gesture. Dragging any node in a multi-selection moves the whole selection together.

Coordinate helpers

ReactorFlow.ScreenToFlowPosition(point, viewport) and FlowToScreenPosition(point, viewport) convert between pane-relative screen points and world/flow coordinates — the React Flow screenToFlowPosition / flowToScreenPosition pair — for hit-testing, drop targets, or placing new nodes under the pointer.

Deriving edges from ports

Instead of maintaining a separate edge list, you can declare a node's outgoing connections on its ports and let ReactorFlow build the edges. Add ReactorFlowPortLink(target, targetPort, label?, animated?) entries to a ReactorFlowPort.Links, then call ReactorFlow.DeriveEdges(nodes):

var nodes = new List<ReactorFlowNode>
{
    new("a", 0, 0, 120, 60, Ports: new[]
    {
        new ReactorFlowPort("out", ReactorFlowPosition.Right,
            Links: new[] { new ReactorFlowPortLink("b", "in", Label: "next") }),
    }),
    new("b", 240, 0, 120, 60, Ports: new[] { new ReactorFlowPort("in", ReactorFlowPosition.Left) }),
};

var edges = ReactorFlow.DeriveEdges(nodes); // one edge: "a:out->b:in"

Edge ids are deterministic ("{node}:{port}->{target}:{targetPort}") and each edge is wired source-port to target-port, so it routes and anchors exactly like an authored edge.

Persisting layout

ctx.UsePersistedFlowLayout(persisted, baseNodes, onCommit, seed?) restores and persists just the runtime-mutable layout — every node's position and size plus the viewport — so the exact arrangement replays later on the same or another machine. Everything else about a node (type, data, ports, parent) is reconstructed from baseNodes and is not persisted. Spread the result onto ReactorFlowProps:

var layout = ctx.UsePersistedFlowLayout(
    persisted: loadedBlob,           // JSON string previously stored, or null
    baseNodes: seedNodes,            // authoritative graph (memoized)
    onCommit: blob => Save(blob),    // called on drag-stop / resize / pan-zoom
    seed: n => (n.X, n.Y));          // placement for nodes new to the blob

return Component<ReactorFlow, ReactorFlowProps>(new(
    Nodes: layout.Nodes,
    Edges: edges,
    Viewport: layout.Viewport,
    OnNodesChange: layout.OnNodesChange,
    OnViewportChange: layout.OnViewportChange));

Commits fire when a drag ends (not on every intermediate frame). The hook is a RenderContext extension, so call it from a function component (Func(ctx => …)) or forward a render context.

Selection & editing

Nodes and edges are selectable and deletable out of the box:

  • Click a node or edge to select it; the selection is highlighted. Edge clicks report through OnEdgeClick and are hit-tested via a transparent hit path, so thin edges are still easy to click.
  • <kbd>Shift</kbd>+drag on the pane rubber-bands a marquee multi-select; <kbd>Shift</kbd>+click toggles a node in the selection (SelectionOnDrag makes a plain drag select).
  • Press <kbd>Delete</kbd> or <kbd>Backspace</kbd> to remove the selection. Deleting a node cascades to its connected edges — the removals are emitted through OnNodesChange / OnEdgesChange, so wire both (with ApplyNodeChanges / ApplyEdgeChanges) to keep your state in sync.
  • Reject invalid connections before they commit with IsValidConnection — return false to veto a drag-to-connect gesture before OnConnect fires.
  • Toggle behavior globally through ReactorFlowOptions: NodesDraggable, NodesSelectable, EdgesSelectable, ElementsDeletable, and FitViewOnInit.

Sub-flows, resizing & big graphs

  • Grouping — set ReactorFlowNode.ParentId to nest a node inside another; its position becomes relative and it moves with the parent. ReactorFlow.GetAbsolutePosition flattens the chain.
  • NodeResizer / NodeToolbar — compose ReactorFlow.NodeResizer(ctx, …) and ReactorFlow.NodeToolbar(ctx, …) inside a Canvas-based custom node for corner resize handles and a floating toolbar shown while selected.
  • Snap & guidesSnapToGrid / SnapGridSize snap dragged nodes to a grid; ShowAlignmentGuides snaps to neighbors' edges/centers and draws guide lines.
  • VirtualizationOnlyRenderVisibleElements culls off-screen nodes/edges for large graphs (ReactorFlow.GetVisibleNodes exposes it as a pure helper).

See the docs for full guides and the API reference.

Options

ReactorFlowOptions controls zoom limits, pan/zoom gestures, handle sizes, corner radius, the connect snap threshold, snap-to-grid, alignment guides, virtualization, and the interaction toggles above. Pass it via ReactorFlowProps.Options.

Sample

The sample app is a self-contained board with several node types (including an interactive stateful counter, a sub-flow group with child nodes, and a resizable node with a NodeToolbar + NodeResizer), ports on all four sides including a labeled-chip custom PortTypes handle, labeled / smooth-step / animated edges with different end markers, a custom EdgeTypes "button" edge (built on BaseEdge) with an inline delete button, drag-to-connect, edge reconnection, marquee multi-select and group multi-drag, alignment guides, pan/zoom, a live event read-out wired to the full event surface, and the Background / MiniMap (pannable, zoomable, node-draggable, type-tinted, with NodeScale emphasis) / Controls / Panel chrome.

dotnet run --project samples/Reactor.Community.ReactorFlow.Sample

License

MIT © 2026 Reactor Community

Product Compatible and additional computed target framework versions.
.NET net10.0-windows10.0.26100 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0-preview.2 36 7/2/2026
0.4.0-preview.1 38 7/2/2026
0.1.0-preview.1 36 7/2/2026