UX Standards — 23: Visual Workflow Builder
Governs: The @xyflow/react (ReactFlow)-based canvas builder at /settings/workflows.
Parent rules: See 00-OVERVIEW-AND-CSS-RULES.md first.
Overview
The Workflow section at /settings/workflows provides a visual interface for creating and managing two types of workflows:
| Type | Description | Builder |
|---|---|---|
| DocType Workflows | ERPNext document state machine — defines document states, allowed actions, and transitions between states | ReactFlow canvas (DocType Workflow Builder) |
| Process Orchestrations | N8N-powered automation flows — trigger-action-gateway pipelines with AI nodes | Planned (JSON/N8N integration) |
The system uses @xyflow/react for the visual canvas. N8N handles the underlying orchestration execution.
Route Structure
/settings/workflows → All Workflows list (tabs: All / Getting Started / Monitor / History / Library)
/settings/workflows/getting-started → Getting Started guide
/settings/workflows/monitor → Process Monitor
/settings/workflows/history → History log
/settings/workflows/library → Workflow Template Library
/settings/workflows/new → Create new workflow
/settings/workflows/[name] → Workflow detail (states/transitions tables)
/settings/workflows/[name]/builder → Visual canvas builder
Workflows List Page
The main /settings/workflows page has 5 tabs across the top and stat cards:
Tabs
| Tab | Route |
|---|---|
| All Workflows | /settings/workflows |
| Getting Started | /settings/workflows/getting-started |
| Monitor | /settings/workflows/monitor |
| History | /settings/workflows/history |
| Library | /settings/workflows/library |
Stat Cards
- DocType Workflows — count of ERPNext workflow definitions
- Orchestrations — count of N8N process orchestrations
Table Columns
Name | Type | Status | DocType / Category | Details
Row actions include "Open Builder" which navigates to /settings/workflows/[name]/builder.
Getting Started Page
Designed for first-time users:
Hero section:
- Title: "Welcome to Workflows"
- CTAs: "Create Your First Workflow" + "Browse Templates"
- Stats bar: Active Workflows | Running Processes | Completed Today | Avg Duration
How It Works (4-step stepper):
- Choose a workflow type
- Design your flow visually
- Set triggers and conditions
- Activate and monitor
Choose Your Workflow Type (cards):
- DocType Workflow — ERPNext document state machine
- Process Orchestration — N8N automation pipeline
Quick Links section:
- Links to documentation, templates, monitor
Process Monitor Page
/settings/workflows/monitor
Stat cards: Total | Running | Waiting | Completed | Failed
Table columns:
Process | Status | Current Step | Progress | Trigger | Started
Progress shown as a progress bar or percentage.
Workflow Library Page
/settings/workflows/library
Browse pre-built workflow templates:
Table columns:
Name | Type | Target / Category | Status | Version | Usage
Row actions: eye (preview) + edit (clone/use template) icons.
DocType Workflow Builder Canvas
The WorkflowBuilderCanvas component renders a full-page ReactFlow canvas for designing ERPNext DocType workflows.
packages/shared → used via:
src/app/(dashboard)/settings/workflows/[name]/builder/components/workflow-builder-canvas.tsx
src/app/(dashboard)/settings/workflows/[name]/builder/components/workflow-canvas-nodes.tsx
src/app/(dashboard)/settings/workflows/[name]/builder/components/workflow-canvas-sidebar.tsx
Full-Page Layout
┌───────────────────────────────────────────────────────────────────┐
│ ← [WorkflowName] [DocType] • N states • M transitions [Unsaved]│ ← Builder Header
│ [💾][↩][↪] | [+][🗑] | [⧉][📋] | [+][−][⛶] | [↓][↑] | [✓][⚙][?] │
├─────────────────────────────────────────────────┬─────────────────┤
│ │ │
│ ReactFlow Canvas │ Properties │
│ (dot grid background) │ Sidebar │
│ │ (w-72) │
│ ┌────┐ [Action] ┌────────┐ │ │
│ │ ●→ │ ────────────────▶ │ State │ │ (no selection)│
│ └────┘ └────────┘ │ Workflow Props │
│ │ + shortcuts │
│ ┌ Element Toolbox ┐ (top-right overlay) │ │
│ │ [State] [Start] │ │ (node select) │
│ │ Drag to canvas │ │ State Props │
│ └─────────────────┘ │ + delete btn │
│ │ (edge select) │
│ [MiniMap - bottom right] │ Transition │
│ [Controls - bottom left] │ Props │
│ [Validation panel - bottom right if errors] │ + delete btn │
└─────────────────────────────────────────────────┴─────────────────┘
Builder Header Bar
Fixed strip across the top with workflow info and toolbar:
Left Zone
- Back button (
ArrowLeft h-4 w-4) — prompts if unsaved changes - Workflow name (
text-lg font-semibold) - Subtitle —
{DocType} • {N} states • {M} transitions(text-xs text-muted-foreground) - Unsaved badge —
Badge variant="outline"withborder-amber-600 text-amber-600when dirty
Right Zone — Toolbar (all Button variant="ghost" size="icon")
| Button | Icon | Notes |
|---|---|---|
| Save | Save h-4 w-4 | Disabled when not dirty |
| (separator) | — | |
| Undo | Undo2 h-4 w-4 | Disabled when stack empty |
| Redo | Redo2 h-4 w-4 | Disabled when stack empty |
| (separator) | — | |
| Add State | Plus h-4 w-4 | Adds new state at center |
| Delete | Trash2 h-4 w-4 | Disabled when nothing selected |
| (separator) | — | |
| Copy | Copy h-4 w-4 | Currently disabled |
| Paste | ClipboardPaste h-4 w-4 | Currently disabled |
| (separator) | — | |
| Zoom In | ZoomIn h-4 w-4 | reactFlowInstance.zoomIn() |
| Zoom Out | ZoomOut h-4 w-4 | reactFlowInstance.zoomOut() |
| Fit View | Maximize2 h-4 w-4 | fitView({ padding: 0.2 }) |
| (separator) | — | |
| Export | Download h-4 w-4 | Export workflow JSON |
| Import | Upload h-4 w-4 | Import workflow JSON |
| (separator) | — | |
| Validate | CheckCircle or AlertCircle h-4 w-4 | Red when errors exist |
| (separator) | — | |
| Toggle Sidebar | Settings h-4 w-4 | Show/hide properties sidebar |
| Help | HelpCircle h-4 w-4 |
Canvas Configuration
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
fitView
fitViewOptions={{ padding: 0.2 }}
connectionLineStyle={{ stroke: '#9ca3af', strokeWidth: 2 }}
>
<Background variant={BackgroundVariant.Dots} gap={20} size={1} />
<MiniMap nodeColor={(node) => ...} className='!bg-background/80' />
<Controls position='bottom-left' showZoom={false} showFitView={false} showInteractive={false} />
</ReactFlow>
| Feature | Value |
|---|---|
| Background | Dots variant, gap={20}, size={1} |
| MiniMap | Bottom-right, color-coded by node state style |
| Controls | Bottom-left (custom — built-in zoom/fit controls hidden) |
| Default edge | transitionEdge type, ArrowClosed marker, #9ca3af |
| Fit on load | fitView with padding: 0.2 |
Node Types
Start Node (startNode)
Green filled circle (32×32px):
h-8 w-8 rounded-full bg-green-500
- Contains a play (▶) icon in white
- Has source handles on Right and Bottom
- Selection ring:
ring-2 ring-blue-500 ring-offset-2
State Node (stateNode)
Rounded rectangle with label:
| Style | Background | Text | Border | When |
|---|---|---|---|---|
| Default | bg-white dark:bg-gray-800 | text-gray-900 | border-gray-300 | Normal states |
| Success | bg-green-500 | text-white | border-green-600 | doc_status === '1' |
| Danger | bg-red-500 | text-white | border-red-600 | doc_status === '2' |
| Primary | (default) | — | — | New, Received |
| Warning | (default + amber ring) | — | — | Pending Review states |
| Info | (default + cyan ring) | — | — | In Progress states |
Content:
- Label:
text-xs font-medium - Role badge (below label): user icon +
allow_editrole name intext-[10px] - Handles on all 4 sides (top/bottom: source+target, left/right: source+target)
- Selection ring:
ring-1 ring-blue-500 ring-offset-1
Auto-Style Inference
State nodes are automatically styled based on their state name and doc_status:
| Condition | Style |
|---|---|
doc_status === '2' | Danger |
doc_status === '1' | Success |
State name in ['New', 'Received'] | Primary |
State name contains Confirmed, Completed, High Confidence, Human Approved, Processed, Routed | Success |
State name contains Cancelled, Declined, Failed, Lost, Spam | Danger |
State name contains Pending Review, Awaiting, Human Corrected | Warning |
State name contains Checking, Classifying, Extracting, In Progress | Info |
Edge Types
Transition Edge (transitionEdge)
Dashed line with arrowhead and label:
- Line style:
strokeDasharray="5 3", color#b0b0b0(selected:#3b82f6) - Stroke width: 1 normal, 1.5 selected
- Arrowhead:
ArrowClosedvia SVG marker - Label: Action name displayed in a
foreignObjectat the midpoint- Label box:
bg-white dark:bg-gray-800,border-gray-200 dark:border-gray-600 - Label text:
text-[10px] font-medium text-gray-600 - Selected label:
border-blue-400 text-blue-600
- Label box:
Element Toolbox (Overlay)
Positioned absolute right-4 top-4 z-10 on the canvas:
┌─────────────────────┐
│ Elements │ ← title (text-xs text-gray-500)
├─────────────────────┤
│ [State] State │ ← draggable item (stateNode)
│ [●] Start │ ← draggable item (startNode)
├─────────────────────┤
│ Drag onto canvas │ ← hint text
└──────── ─────────────┘
- Width:
w-40 - Card:
rounded-lg border bg-white dark:bg-gray-900 p-3 shadow-lg - Draggable items:
cursor-grab, hover highlights (blue for state, green for start) - Drag behavior:
onDragStartsetsdataTransfer.setData('application/reactflow', nodeType) - Drop target:
onDropon the canvas wrapper converts screen coords to flow coords viareactFlowInstance.screenToFlowPosition()
Properties Sidebar
Right-side panel, w-72, with overflow-y-auto border-l bg-card:
No Selection — Workflow Properties
- Document Type (read-only label)
- Is Active (Checkbox)
- Don't Override Status (Checkbox)
- Send Email Alert (Checkbox)
- Keyboard shortcuts reminder
Node Selected — State Properties
| Field | Component |
|---|---|
| State Name | Input (read-only) |
| Doc Status | Select (Draft/Submitted/Cancelled) |
| Only Allow Edit For | Input (read-only) |
| Update Field | Input (read-only, optional) |
| Update Value | Input (read-only, optional) |
| Delete State | Button variant="destructive" full-width |
Edge Selected — Transition Properties
| Field | Component |
|---|---|
| Action | Input (read-only) |
| Allowed Role | Input (read-only) |
| Allow Self Approval | Checkbox |
| Condition | Textarea rows={4} (monospace, read-only) |
| Delete Transition | Button variant="destructive" full-width |
Validation Panel
Shown in bottom-right Panel when validationErrors.length > 0:
<div className='max-w-sm rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-950'>
<div className='mb-2 flex items-center gap-2 text-sm font-medium text-red-800 dark:text-red-200'>
<AlertCircle className='h-4 w-4' />
{validationErrors.length} validation issue(s)
</div>
<ul className='space-y-1 text-xs text-red-700 dark:text-red-300'>
{validationErrors.slice(0, 5).map((error) => (
<li>• {error}</li>
))}
{validationErrors.length > 5 && <li>...and N more</li>}
</ul>
</div>
Validation checks:
- States with no transitions (isolated nodes)
- Workflow should have an initial state (no incoming edges)
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Ctrl/⌘ + S | Save workflow |
Delete / Backspace | Delete selected node or edge |
N | Add new state at canvas center |
Escape | Deselect current selection |
Drag & Drop
Nodes can be dragged from the Element Toolbox onto the canvas:
draggableattribute on toolbox itemsonDragStartsets node type indataTransfer- Canvas
onDropconverts cursor position to flow coordinates - New node created at drop position with default data
Data Model
// ERPNext Workflow
interface Workflow {
workflow_name: string;
document_type: string;
is_active: 0 | 1;
override_status: 0 | 1;
send_email_alert: 0 | 1;
workflow_data?: string; // JSON string of node positions
states: WorkflowState[];
transitions: WorkflowTransition[];
}
// State Node Data
interface StateNodeData {
label: string;
state: WorkflowState;
style: '' | 'Primary' | 'Success' | 'Danger' | 'Warning' | 'Info';
hasError?: boolean;
hasWarning?: boolean;
}
// Transition Edge Data
interface TransitionEdgeData {
transition: WorkflowTransition;
actionLabel: string;
roleLabel: string;
hasCondition: boolean;
allowSelfApproval: boolean;
}
MiniMap Color Coding
| Style | Color |
|---|---|
| Success | #22c55e (green-500) |
| Danger | #ef4444 (red-500) |
| Warning | #f59e0b (amber-500) |
| Primary | #3b82f6 (blue-500) |
| Info | #06b6d4 (cyan-500) |
| Default | #9ca3af (gray-400) |
CSS Tokens for Workflow Nodes
Workflow node colors are defined in _variables.css under --node-* tokens:
/* Start Node - Green */
--node-start: 142 76% 36%;
--node-start-bg: 142 76% 95%;
--node-start-border: 142 76% 70%;
/* Gateway Node - Amber */
--node-gateway: 38 92% 50%;
--node-gateway-bg: 38 92% 95%;
--node-gateway-border: 38 92% 75%;
/* Action Node - Emerald */
--node-action: 160 84% 39%;
/* Timer Node - Indigo */
--node-timer: 239 84% 67%;
/* Signal Node - Pink */
--node-signal: 330 81% 60%;
/* ... etc */
See 24-GLOBAL-CSS-AND-DESIGN-TOKENS.md for the complete token list.
Violation Checklist
- Uses
@xyflow/react(NOTreactflowv10 or older) —import from '@xyflow/react' - Canvas background:
BackgroundVariant.Dotswithgap={20} size={1} - MiniMap:
className='!bg-background/80' - Controls:
position='bottom-left'with all built-in buttons hidden - Element Toolbox:
absolute right-4 top-4 z-10 w-40 - Start node:
h-8 w-8 rounded-full bg-green-500 - State node: rounded rectangle, min-width 100px, auto-styled by
inferStyle() - Transition edge:
strokeDasharray="5 3", label at midpoint inforeignObject - Properties sidebar:
w-72 border-l bg-card - Save shortcut:
Ctrl/⌘+S - Delete shortcut:
Delete/Backspaceon selected element - "Unsaved" badge:
border-amber-600 text-amber-600 - Validation panel:
bg-red-50 dark:bg-red-950 border-red-200 - Back button prompts if dirty:
confirm(t('popup.workflow.unsavedWarning'))