Vyuh Node Flow

January 4, 2026 · View on GitHub

Vyuh Node Flow Banner

A flexible, high-performance node-based flow editor for Flutter applications. Build visual programming interfaces, workflow editors, interactive diagrams, and data pipelines with ease.

Pub Version Documentation License

Try the Live Demo · Get Started


Vyuh Node Flow in Action


Features

  • High Performance — Optimized rendering for smooth 60fps interactions
  • Viewport Controls — Pan, zoom, fit-to-view with animated transitions
  • Smart Connections — Bezier, smoothstep, step paths with validation rules
  • Connection Effects — Particles, flowing dashes, gradients, pulse & rainbow
  • Comprehensive Theming — Nodes, ports, connections, grid backgrounds & more
  • Keyboard Shortcuts — 20+ built-in actions, fully customizable
  • Multi-Select — Marquee selection, copy/paste, and bulk operations
  • Minimap & LOD — Bird's-eye navigation and zoom-based detail levels
  • Special Nodes — Groups for organization, comments for annotations
  • Serialization — Save and load flows from JSON with type-safe generics

Showcase

Demo Application
Demo Application
Full-featured demo with 20+ examples
Connection Effects
Connection Effects
Particles, gradients, flowing dashes & more
Minimap Navigation
Minimap Navigation
Bird's-eye view for large graphs
Level of Detail
Level of Detail
Auto-hide details when zoomed out
Viewport Animations
Viewport Animations
Smooth pan, zoom & fit-to-view
Theming System
Theming System
Light, dark & custom themes

Installation

dependencies:
  vyuh_node_flow: ^0.20.0

Quick Start

import 'package:flutter/material.dart';
import 'package:vyuh_node_flow/vyuh_node_flow.dart';

class FlowEditor extends StatefulWidget {
  @override
  State<FlowEditor> createState() => _FlowEditorState();
}

class _FlowEditorState extends State<FlowEditor> {
  late final controller = NodeFlowController<String, dynamic>(
    nodes: [
      Node<String>(
        id: 'node-1',
        type: 'input',
        position: const Offset(100, 100),
        data: 'Start',
        outputPorts: const [
          Port(id: 'out', name: 'Output', offset: Offset(2, 40)),
        ],
      ),
      Node<String>(
        id: 'node-2',
        type: 'output',
        position: const Offset(400, 100),
        data: 'End',
        inputPorts: const [
          Port(id: 'in', name: 'Input', offset: Offset(-2, 40)),
        ],
      ),
    ],
    connections: [
      Connection(
        id: 'conn-1',
        sourceNodeId: 'node-1',
        sourcePortId: 'out',
        targetNodeId: 'node-2',
        targetPortId: 'in',
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return NodeFlowEditor<String, dynamic>(
      controller: controller,
      theme: NodeFlowTheme.light,
      nodeBuilder: (context, node) => Padding(
        padding: const EdgeInsets.all(16),
        child: Text(node.data, style: const TextStyle(fontWeight: FontWeight.bold)),
      ),
    );
  }
}

Tip

Use a sealed class hierarchy for node data to get full type safety and pattern matching in your node builder.


Theming

Customize every visual aspect with the comprehensive theming system:

final customTheme = NodeFlowTheme.dark.copyWith(
  // Connection appearance
  connectionTheme: ConnectionTheme.dark.copyWith(
    style: ConnectionStyles.bezier,
    color: Colors.purple.shade300,
    animationEffect: ConnectionEffects.particles,
  ),

  // Grid background
  gridTheme: GridTheme.dark.copyWith(
    style: GridStyles.dots,
    color: Colors.white24,
  ),

  // Node styling
  nodeTheme: NodeTheme.dark.copyWith(
    borderRadius: BorderRadius.circular(12),
    selectedBorderColor: Colors.purple,
  ),
);

Note

See the Theming Guide for all available theme options including port shapes, connection effects, and custom builders.


Events

React to user interactions with the event system:

NodeFlowEditor<MyData, dynamic>(
  controller: controller,
  theme: theme,
  nodeBuilder: _buildNode,
  events: NodeFlowEvents<MyData, dynamic>(
    // Node events
    node: NodeEvents<MyData, dynamic>(
      onTap: (node) => print('Tapped: ${node.id}'),
      onMove: (node, delta) => print('Moving: ${node.id}'),
    ),

    // Connection validation
    connection: ConnectionEvents<MyData, dynamic>(
      onBeforeComplete: (context) {
        // Prevent self-connections
        if (context.sourceNode.id == context.targetNode.id) {
          return ConnectionValidationResult.invalid(
            reason: 'Cannot connect node to itself',
          );
        }
        return ConnectionValidationResult.valid();
      },
      onCreated: (connection) => print('Connected: ${connection.id}'),
    ),

    // Selection events
    selection: SelectionEvents<MyData, dynamic>(
      onChanged: (nodes) => print('Selected: ${nodes.length} nodes'),
    ),
  ),
);

Note

See the Events Guide for the complete event API including viewport, keyboard, and graph events.


Extensions

Add features through the modular extension system:

NodeFlowEditor<MyData, dynamic>(
  controller: controller,
  theme: theme,
  nodeBuilder: _buildNode,
  extensions: [
    // Navigation minimap
    MinimapExtension(visible: true),

    // Performance statistics overlay
    StatsExtension(),

    // Visual debugging overlays
    DebugExtension(mode: DebugMode.all),

    // Level of detail rendering
    LodExtension(enabled: true),
  ],
);

Documentation

For comprehensive guides and API reference, visit the documentation:

TopicDescription
InstallationSetup and requirements
Core ConceptsNodes, ports, connections, and controller
ThemingComplete visual customization
Connection EffectsParticles, flowing dashes, gradients & more
EventsInteraction callbacks and validation
SerializationSave and load flows
Special NodesComments, groups, and annotations
APIComplete API documentation
ExamplesWorking code examples

Acknowledgments

Vyuh Node Flow is inspired by React Flow, the excellent node-based graph library for React.


License

MIT License - see LICENSE for details.


Made with ❤️ by the Vyuh team