FlowState 0.1.10-alpha

This is a prerelease version of FlowState.
dotnet add package FlowState --version 0.1.10-alpha
                    
NuGet\Install-Package FlowState -Version 0.1.10-alpha
                    
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="FlowState" Version="0.1.10-alpha" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FlowState" Version="0.1.10-alpha" />
                    
Directory.Packages.props
<PackageReference Include="FlowState" />
                    
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 FlowState --version 0.1.10-alpha
                    
#r "nuget: FlowState, 0.1.10-alpha"
                    
#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 FlowState@0.1.10-alpha
                    
#: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=FlowState&version=0.1.10-alpha&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FlowState&version=0.1.10-alpha&prerelease
                    
Install as a Cake Tool

FlowState

A high-performance node editor for Blazor. Build visual programming tools, node-based workflows, and interactive graph editors with complete styling control.

<img width="1217" height="587" alt="FlowState Demo" src="https://github.com/user-attachments/assets/57d6fecb-5d84-4f17-ad90-cee3cf881f48" />

Blazor License NuGet

What you get

  • Full customization - Style everything with regular HTML/CSS, no framework-specific styling
  • High performance - Handles graphs with hundreds of nodes smoothly
  • Custom nodes - Create any node type using Blazor components
  • Type safety - Automatic type checking between connections with custom type conversion
  • Execution engine - Built-in graph execution with visual progress feedback
  • Group nodes - Resizable container nodes to organize your graph
  • Undo/Redo - Command pattern with unlimited history
  • Serialization - Save and restore complete graph state
  • Read-only mode - Lock graphs for presentation
  • Touch support - Works on desktop and mobile

Customization Demo

⚠️ EXPERIMENTAL - This project is in active development and the API may change. Builds are nightly and super frequent and may contain breaking changes. Use in production at your own risk.

Documentation

Installation

dotnet add package FlowState

In blazor server make sure your page render mode is interactive server

@rendermode InteractiveServer	

Quick Start

Here's a minimal working example:

@page "/"
@using FlowState.Components
@using FlowState.Models

<FlowCanvas Height="100vh" Width="100vw" Graph="graph">
    <BackgroundContent>
        <FlowBackground class="grid-bg"/>
    </BackgroundContent>
</FlowCanvas>

@code {
    FlowGraph graph = new();

    protected override void OnInitialized() 
    {
        graph.RegisterNode<SumNode>();
    }
}

Style the canvas with CSS:

.grid-bg {
    background: #111827;
    background-image: 
        linear-gradient(rgba(255,255,255,0.1) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,0.1) 1px, transparent 1px);
    background-size: 100px 100px;
}

Creating Custom Nodes

Making a node is simple. Create a Razor component that inherits FlowNodeBase:

SumNode.razor.cs

using FlowState.Components;
using FlowState.Attributes;
using FlowState.Models.Execution;

namespace YourNamespace;

[FlowNodeMetadata(Category = "Math", Title = "Sum", Icon = "➕")]
public partial class SumNode : FlowNodeBase
{
    public override async ValueTask ExecuteAsync(FlowExecutionContext context)
    {
        var a = context.GetInputSocketData<float>("A");
        var b = context.GetInputSocketData<float>("B");
        context.SetOutputSocketData("Result", a + b);
        await ValueTask.CompletedTask;
    }
}

SumNode.razor

@using FlowState.Components
@using FlowState.Models
@inherits FlowNodeBase

<FlowNode>
    <div class="title">➕ Sum</div>
    <div class="body">
        <FlowSocket Name="A" Type="SocketType.Input" T="typeof(float)"/>
        <FlowSocket Name="B" Type="SocketType.Input" T="typeof(float)"/>
        <FlowSocket Name="Result" Type="SocketType.Output" T="typeof(float)"/>
    </div>
</FlowNode>

That's it. The node will appear in the context menu (right-click on canvas) and you can connect it to other nodes.

Working with Graphs

Creating nodes programmatically:

// Create nodes at specific positions
var node1 = await graph.CreateNodeAsync<SumNode>(100, 100, new());
var node2 = await graph.CreateNodeAsync<DisplayNode>(400, 100, new());

// Connect them
await graph.ConnectAsync(node1.Id, node2.Id, "Result", "Input");

// Execute the graph
await graph.ExecuteAsync();

Save and restore:

// Serialize to JSON
string json = await graph.SerializeAsync();
await File.WriteAllTextAsync("graph.json", json);

// Deserialize from JSON
string json = await File.ReadAllTextAsync("graph.json");
await graph.DeserializeAsync(json);

Type conversion for sockets:

// Allow int to connect to float sockets
graph.TypeCompatibiltyRegistry.Register<float>(typeof(int));

// Sockets with type 'object' can connect to anything
<FlowSocket Name="Any" Type="SocketType.Input" T="typeof(object)"/>

Canvas Configuration

Customize the canvas behavior:

<FlowCanvas Graph="graph"
            Height="100vh"
            Width="100vw"
            IsReadOnly="false"
            MinZoom="0.2"
            MaxZoom="2.0"
            Zoom="1.0"
            PanKey="alt"
            NodeSelectionClass="selected"
            AutoUpdateSocketColors="true"/>

API Reference

Graph operations:

// Nodes
var node = await graph.CreateNodeAsync<MyNode>(x, y, data);
await graph.RemoveNodeAsync(nodeId);
var node = graph.GetNodeById(nodeId);
graph.RegisterNode<MyNode>();

// Connections
var (edge, error) = await graph.ConnectAsync(fromId, toId, "Out", "In");
await graph.RemoveEdgeAsync(edgeId);

// Execution
await graph.ExecuteAsync();

// Undo/Redo
await graph.CommandManager.UndoAsync();
await graph.CommandManager.RedoAsync();

Node lifecycle hooks:

MyNode.razor.cs

using FlowState.Components;
using FlowState.Models.Execution;

public partial class MyNode : FlowNodeBase
{
    // Called before graph execution starts - reset state here
    public override ValueTask BeforeGraphExecutionAsync() 
    {
        // Reset any cached state
        return ValueTask.CompletedTask;
    }

    // Main execution logic
    public override async ValueTask ExecuteAsync(FlowExecutionContext ctx)
    {
        var input = ctx.GetInputSocketData<float>("In");
        ctx.SetOutputSocketData("Out", input * 2);
        await ValueTask.CompletedTask;
    }
}

MyNode.razor

@using FlowState.Components
@using FlowState.Models
@inherits FlowNodeBase

<FlowNode>
    <div class="title">My Node</div>
    <div class="body">
        <FlowSocket Name="In" Type="SocketType.Input" T="typeof(float)"/>
        <FlowSocket Name="Out" Type="SocketType.Output" T="typeof(float)"/>
    </div>
</FlowNode>

Events

Graph events:

graph.NodeAdded += (s, e) => { };
graph.NodeRemoved += (s, e) => { };
graph.EdgeAdded += (s, e) => { };
graph.EdgeRemoved += (s, e) => { };

Canvas events:

<FlowCanvas OnCanvasLoaded="..."
            OnNodeMoved="..."
            OnNodeSelected="..."
            OnSelectionChanged="..."
            OnContextMenu="..."
            OnSocketLongPress="..."/>

Advanced Features

Control panels with zoom/reset buttons:

Add UI controls for canvas interaction:

@using FlowState.Components
@using FlowState.Models

<FlowCanvas Graph="graph" Height="100vh" Width="100vw">
    <BackgroundContent>
        <FlowBackground class="grid-bg"/>
    </BackgroundContent>
    <Panels>
        <FlowPanels>
            
            <div class="panel-group">
                <button class="panel-btn" @onclick="SaveGraph">💾</button>
                <button class="panel-btn" @onclick="LoadGraph">📂</button>
            </div>
        </FlowPanels>
    </Panels>
</FlowCanvas>

@code {
    FlowGraph graph = new();
    
    async Task SaveGraph() => await File.WriteAllTextAsync("graph.json", await graph.SerializeAsync());
    async Task LoadGraph() => await graph.DeserializeAsync(await File.ReadAllTextAsync("graph.json"));
}

Context menu for node creation:

Add a context menu so users can right-click to create nodes:

@using FlowState.Components
@using FlowState.Models
@using FlowState.Models.Events

<FlowCanvas Graph="graph" 
            Height="100vh" 
            Width="100vw"
            OnContextMenu="ShowMenu">
    <BackgroundContent>
        <FlowBackground class="grid-bg"/>
    </BackgroundContent>
</FlowCanvas>

<FlowContextMenu @ref="menu" Graph="graph"/>

@code {
    FlowGraph graph = new();
    FlowContextMenu? menu;
    
    protected override void OnInitialized()
    {
        graph.RegisterNode<SumNode>();
        graph.RegisterNode<DisplayNode>();
    }
    
    async Task ShowMenu(CanvasContextMenuEventArgs e) =>
        await menu!.ShowAsync(e.ClientX, e.ClientY, e.X, e.Y);
}

Group nodes:

Group nodes are resizable containers to organize your graph.

MyGroupNode.razor.cs

using FlowState.Components;
using FlowState.Attributes;

namespace YourNamespace;

[FlowNodeMetadata(Category = "Layout", Title = "Group", Kind = NodeKind.Group)]
public partial class MyGroupNode : FlowGroupNodeBase
{
    protected override void OnInitialized()
    {
        base.OnInitialized();
        Width = 400;
        Height = 300;
    }
}

MyGroupNode.razor

@using FlowState.Components
@inherits FlowGroupNodeBase

<FlowNode>
    <div class="group-title">📦 Group</div>
    <FlowResizeHandle />
</FlowNode>

Style group nodes to appear behind other nodes:

.flow-node[kind="Group"] {
    z-index: 1 !important;
    background: rgba(50, 50, 50, 0.5);
    border: 2px dashed rgba(255, 255, 255, 0.3);
}

Execution state management:

Share data between nodes during execution:

StateNode.razor.cs

using FlowState.Components;
using FlowState.Models.Execution;

public partial class StateNode : FlowNodeBase
{
    public override async ValueTask ExecuteAsync(FlowExecutionContext ctx)
    {
        // Store and retrieve shared state across all nodes in execution
        ctx.SetState("userCount", 42);
        var count = ctx.GetState<int>("userCount");
        
        // Or use CustomData dictionary directly
        ctx.CustomData["result"] = "computed value";
        var data = ctx.CustomData["result"];
        
        await ValueTask.CompletedTask;
    }
}

StateNode.razor

@using FlowState.Components
@inherits FlowNodeBase

<FlowNode>
    <div class="title">State Manager</div>
    <div class="body">
        <FlowSocket Name="Execute" Type="SocketType.Exec" T="typeof(object)"/>
    </div>
</FlowNode>

Examples

Check out the example projects in the repo:

  • Blazor Server - Full-featured editor with toolbar and custom nodes
  • Blazor WASM - Lightweight browser-only version
  • Unity themed nodes - Custom styled nodes (ocean wave renderer, color gradients)

Run them with:

cd examples/FlowStateBlazorServer
dotnet run

Contributing

Found a bug or want to add a feature? Pull requests are welcome. For major changes, open an issue first.

License

MIT


Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
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.1.10-alpha 47 11/1/2025
0.1.9-alpha 109 10/26/2025
0.1.8-alpha 83 10/25/2025
0.1.7-alpha 43 10/25/2025
0.1.6-alpha 45 10/25/2025
0.1.5-alpha 55 10/25/2025
0.1.4-alpha 49 10/25/2025
0.1.3-alpha 62 10/24/2025
0.1.2-alpha 69 10/24/2025
0.1.1-alpha 68 10/24/2025
0.1.0-alpha 93 10/24/2025
0.0.7 125 10/23/2025
0.0.6 123 10/23/2025
0.0.5 124 10/22/2025
0.0.4 118 10/21/2025
0.0.3 122 10/20/2025
0.0.2 117 10/19/2025
0.0.1 124 10/19/2025