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

Node

A step card with a kind icon or brand logo, a status light, handles, a model chip and a timer.

import { WorkflowNodeCard } from "../components/workflow-node";
import type { WorkflowNodeDemo } from "../types/workflow";

const EXAMPLES: WorkflowNodeDemo[] = [
  {
    node: {
      description: "Every Monday at 09:00",
      id: "trigger",
      kind: "trigger",
      position: { x: 0, y: 0 },
      title: "Weekly schedule",
    },
    state: { elapsedMs: 0, status: "idle" },
  },
  {
    node: {
      description: "Is this a listicle we fit in?",
      id: "classify",
      kind: "ai",
      model: "claude-sonnet-5-5",
      position: { x: 0, y: 0 },
      title: "Classify fit",
    },
    state: { elapsedMs: 1400, status: "running" },
  },
  {
    node: {
      description: "Searches 12 best-of prompts.",
      id: "search",
      kind: "integration",
      logo: "browserbase",
      position: { x: 0, y: 0 },
      title: "Search listicles",
    },
    state: { elapsedMs: 1800, status: "success" },
  },
  {
    node: {
      branch: "no",
      id: "listed",
      kind: "condition",
      outputs: [
        {
          hint: "Update the tracker",
          id: "yes",
          label: "Already listed",
          rate: 0.38,
        },
        {
          hint: "Find the site owner and pitch",
          id: "no",
          label: "Not listed",
          rate: 0.62,
        },
      ],
      position: { x: 0, y: 0 },
      rule: { operator: "mentions", subject: "Listicle", value: "Notra" },
      title: "Are we listed?",
    },
    state: { elapsedMs: 400, status: "success" },
  },
  {
    node: {
      description: "Waits for someone on the team.",
      id: "review",
      kind: "approval",
      position: { x: 0, y: 0 },
      title: "Review draft",
    },
    state: { elapsedMs: 3200, status: "waiting" },
  },
  {
    node: {
      description: "From your outreach inbox",
      id: "send",
      kind: "email",
      logo: "resend",
      position: { x: 0, y: 0 },
      title: "Send outreach",
    },
    state: { elapsedMs: 600, status: "error" },
  },
];

export default function WorkflowNodeExample() {
  return (
    <div className="flex w-full max-w-3xl flex-wrap items-start justify-center gap-6 px-3">
      {EXAMPLES.map(({ node, state }) => (
        <WorkflowNodeCard key={node.id} node={node} state={state} />
      ))}
    </div>
  );
}
$ bunx --bun shadcn@latest add @notra/workflow-node

WorkflowNodeCard is the step the canvas draws for every node, built on the shell card. The inner card shows the step’s kind, title, description and a status light that glows while the step runs. The footer on the frame holds chips for the integration or model and how long the step takes. AI steps add a dice icon to the model chip to mark them as non-deterministic, and failed or waiting steps get a banner above the card.

Steps without labeled ports get one input and one output handle in the header. Give inputs labels to stack a row per input, which is how a merge takes several inputs. Labeled outputs become branches in a rail under the card, which is how an if/else or a decision routes the run. Each branch can say what happens next with hint and how often it’s taken with rate, and the branch a run takes lights up.

Give an if/else a rule to spell out what it checks in plain words, like { subject: "Listicle", operator: "mentions", value: "Notra" }.

Prop Default Description
node The step: id, kind, title, position and optional description, logo, model, rule, inputs, outputs, branch and durationMs.
state { status, elapsedMs }. Running and waiting steps tick their timer.
selected Draws the selection ring.
connectable Renders React Flow handles. Leave it off to use the card outside a canvas, like in a list or a preview.

WorkflowFlowNode is the React Flow node type that wraps the card with connectable on. Register it as a nodeType to use workflow steps in your own ReactFlow.

kind is one of trigger, task, ai, integration, condition, switch, merge, loop, delay, approval, email or output.

Ports

"use client";

import { WorkflowCanvas } from "../components/workflow-canvas";
import { useWorkflowGraph } from "../hooks/use-workflow-graph";
import type { WorkflowGraph } from "../types/workflow";

const GRAPH: WorkflowGraph = {
  edges: [
    { id: "e1", source: "traffic", target: "summary", targetPort: "traffic" },
    {
      id: "e2",
      source: "visibility",
      target: "summary",
      targetPort: "visibility",
    },
    { id: "e3", source: "summary", target: "route" },
    {
      id: "e4",
      label: "≥ 10%",
      source: "route",
      sourcePort: "up",
      target: "slack",
    },
    { id: "e5", source: "route", sourcePort: "flat", target: "sheet" },
    {
      id: "e6",
      label: "≤ -10%",
      source: "route",
      sourcePort: "down",
      target: "linear",
    },
  ],
  nodes: [
    {
      id: "traffic",
      kind: "integration",
      logo: "posthog",
      position: { x: 32, y: 72 },
      title: "AI referral traffic",
    },
    {
      id: "visibility",
      kind: "task",
      position: { x: 32, y: 288 },
      title: "Visibility report",
    },
    {
      id: "summary",
      inputs: [
        { id: "traffic", label: "Traffic" },
        { id: "visibility", label: "Visibility" },
      ],
      kind: "ai",
      model: "claude-haiku-5-5",
      position: { x: 352, y: 144 },
      title: "Write digest",
    },
    {
      id: "route",
      kind: "switch",
      outputs: [
        { hint: "Share the win", id: "up", label: "Visibility up" },
        { hint: "Add to the sheet", id: "flat", label: "No change" },
        { hint: "Open a Linear issue", id: "down", label: "Visibility down" },
      ],
      position: { x: 680, y: 144 },
      rule: {
        operator: "compared to",
        subject: "AI visibility",
        value: "last week",
      },
      title: "Week over week",
    },
    {
      id: "slack",
      kind: "integration",
      logo: "slack",
      position: { x: 1056, y: 96.5 },
      title: "Celebrate in #geo",
    },
    {
      id: "sheet",
      kind: "integration",
      logo: "google-sheets",
      position: { x: 1056, y: 272.5 },
      title: "Log to sheet",
    },
    {
      id: "linear",
      kind: "integration",
      logo: "linear",
      position: { x: 1056, y: 432.5 },
      title: "Open an issue",
    },
  ],
};

export default function WorkflowPortsExample() {
  const graph = useWorkflowGraph(GRAPH);
  return (
    <WorkflowCanvas
      className="h-80 w-full"
      edges={graph.edges}
      nodes={graph.nodes}
      onConnect={graph.connect}
      onNodeMove={graph.moveNode}
    />
  );
}
{
  id: "route",
  kind: "switch",
  title: "Week over week",
  outputs: [
    { id: "up", label: "Visibility up" },
    { id: "flat", label: "No change" },
    { id: "down", label: "Visibility down" },
  ],
}

Used in

GEO Workflow Builder

A workflow builder with a block palette, a playable canvas, a run log and a node sheet.