Skip to content
Notra logoNotra UI
Esc
↑↓navigate↵open⌘Jpreview
On this page

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-canvas

WorkflowCanvas 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.