Canvas
A React Flow canvas with workflow nodes, animated edges, zoom controls and grid styles.
"use client";
import { WorkflowCanvas } from "../components/workflow-canvas";
import { WorkflowRunButton } from "../components/workflow-run-button";
import { WorkflowTimer } from "../components/workflow-timer";
import { SCHEMA_AUDIT_WORKFLOW } from "../constants/workflow-templates";
import { useWorkflowGraph } from "../hooks/use-workflow-graph";
import { useWorkflowRun } from "../hooks/use-workflow-run";
export default function WorkflowCanvasExample() {
const graph = useWorkflowGraph(SCHEMA_AUDIT_WORKFLOW);
const run = useWorkflowRun(graph.nodes, graph.edges);
return (
<div className="flex w-full flex-col gap-3">
<div className="flex items-center justify-between">
<WorkflowRunButton
onPlay={run.play}
onStop={run.stop}
phase={run.phase}
progress={run.elapsedMs / run.totalMs}
/>
<WorkflowTimer ms={run.elapsedMs} running={run.phase === "running"} />
</div>
<WorkflowCanvas
className="h-56"
edgeState={run.edgeState}
edges={graph.edges}
nodeState={run.nodeState}
nodes={graph.nodes}
onConnect={graph.connect}
onNodeMove={graph.moveNode}
/>
</div>
);
}
$ bunx --bun shadcn@latest add @notra/workflow-canvasWorkflowCanvas is a React Flow canvas set up for workflows. Pass it your steps and connections and it renders every step as a WorkflowNodeCard and every connection as a WorkflowEdge. Press Run to play the workflow from left to right, drag a node to move it, or drag from an output handle to an input handle to connect two steps. The canvas opens fitted to the graph, and when a long workflow would get too small to read it starts at the first step instead and you scroll along.
const graph = useWorkflowGraph(SCHEMA_AUDIT_WORKFLOW);
const run = useWorkflowRun(graph.nodes, graph.edges);
<WorkflowCanvas
nodes={graph.nodes}
edges={graph.edges}
nodeState={run.nodeState}
edgeState={run.edgeState}
onNodeMove={graph.moveNode}
onConnect={graph.connect}
/>
| Prop | Default | Description |
|---|---|---|
nodes |
Steps, each with an id, kind, title and position. |
|
edges |
Connections, each with a source, target and optional sourcePort, targetPort, label and animated. |
|
nodeState |
Returns { status, elapsedMs } for a step id. |
|
edgeState |
Returns idle, active, done or skipped for an edge id. |
|
background |
dots |
dots, lines, grid, cross or none. |
gap |
16 |
Space between dots or lines. |
focusId |
Scrolls a step into view, like one you just added. | |
selectedId |
Draws the selection ring on a step. | |
onSelectNode |
Called when a step is clicked. | |
onNodeMove |
Makes steps draggable. | |
onNodeRemove |
Lets Backspace delete the selected step. | |
onConnect |
Turns on drag-to-connect and gets the new edge. | |
onDropBlock |
Called with a block id and canvas position when a palette block is dropped. | |
controls |
true |
Shows zoom out, zoom in and fit in the corner. |
minimap |
false |
Shows a React Flow minimap in the corner on wider screens. |
toolbar |
Extra content next to the zoom controls. | |
minZoom |
0.3 |
The smallest zoom. |
maxZoom |
1.5 |
The largest zoom. |
The canvas fits the graph on mount and pans when you scroll. It imports React Flow’s base stylesheet, so there’s nothing to add to your CSS.
Grid styles
"use client";
import { useState } from "react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { WorkflowCanvas } from "../components/workflow-canvas";
import { WEEKLY_DIGEST_WORKFLOW } from "../constants/workflow-templates";
import { useWorkflowGraph } from "../hooks/use-workflow-graph";
import type {
WorkflowCanvasBackgroundOption,
WorkflowCanvasBackgroundVariant,
} from "../types/workflow";
const VARIANTS: WorkflowCanvasBackgroundOption[] = [
{ label: "Dots", value: "dots" },
{ label: "Lines", value: "lines" },
{ label: "Grid", value: "grid" },
{ label: "Cross", value: "cross" },
{ label: "None", value: "none" },
];
export default function WorkflowCanvasBackgroundsExample() {
const graph = useWorkflowGraph(WEEKLY_DIGEST_WORKFLOW);
const [variant, setVariant] =
useState<WorkflowCanvasBackgroundVariant>("grid");
return (
<div className="flex w-full flex-col gap-3">
<ToggleGroup
aria-label="Canvas background"
onValueChange={(value) => {
const [next] = value;
const match = VARIANTS.find((item) => item.value === next);
if (match) {
setVariant(match.value);
}
}}
value={[variant]}
variant="outline"
>
{VARIANTS.map((item) => (
<ToggleGroupItem key={item.value} value={item.value}>
{item.label}
</ToggleGroupItem>
))}
</ToggleGroup>
<WorkflowCanvas
background={variant}
className="h-72"
edges={graph.edges}
nodes={graph.nodes}
onConnect={graph.connect}
onNodeMove={graph.moveNode}
/>
</div>
);
}
background picks the grid. grid layers a faint minor grid under a major line every fifth cell, like a design tool. All styles mix --foreground into transparent, so they work in light and dark mode. WorkflowCanvasBackground is exported on its own if you want the same grids in your own React Flow.
<WorkflowCanvas background="grid" gap={24} nodes={nodes} edges={edges} />
Playing a workflow
useWorkflowRun(nodes, edges) schedules every step ahead of time. Steps start once all of their taken inputs finish, so parallel branches run side by side and a merge waits for both. Conditions and decisions follow their branch, and everything behind a branch that wasn’t taken is skipped. Each node runs for its durationMs. Replace the hook with your engine’s run events to show a real run.
useWorkflowGraph(template) keeps the nodes and edges editable with moveNode, addNode, removeNode, updateNode and connect.
Used in
GEO Workflow Builder
A workflow builder with a block palette, a playable canvas, a run log and a node sheet.