Trazo docs

API Reference

Types, functions, and the React component for @joycostudio/trazo.

Entry points

import {
  layoutFlow, layoutGit, layoutSequence, layoutBlock, layout,
  flow, git, seq, block, // tagged-template parsers (throw on error)
} from "@joycostudio/trazo";
import { Graph } from "@joycostudio/trazo/react"; // React is a peer dependency

Layout functions

layoutFlow(input, options?)

Lay out a flowchart graph and return a positioned result ready for rendering.

layoutFlow(input: FlowGraph, options?: FlowLayoutOptions): PositionedGraph

layoutGit(input, options?)

Lay out a commit DAG.

layoutGit(input: CommitGraph, options?: LayoutOptions): PositionedGraph

layoutSequence(input, options?)

Lay out a sequence diagram (participants + ordered messages + notes).

layoutSequence(input: SequenceGraph, options?: SequenceLayoutOptions): PositionedGraph

layoutBlock(input, options?)

Lay out a block-grid wireframe (Mermaid block-beta).

layoutBlock(input: BlockGraph, options?: BlockLayoutOptions): PositionedGraph

layout(input, options?)

Convenience dispatcher — routes by input.kind: "flow"layoutFlow, "sequence"layoutSequence, "block"layoutBlock, otherwise layoutGit.

layout(input: CommitGraph | FlowGraph | SequenceGraph | BlockGraph, options?): PositionedGraph

Tagged templates

flow, git, seq, and block parse DSL source inline and throw on a syntax error (vs parseFlow/parseGit/parseSequence/parseBlock, which return { graph, error }):

import { seq, layoutSequence } from "@joycostudio/trazo";

const positioned = layoutSequence(seq`
  participant Client
  participant Server
  Client ->> Server : request
  Server -->> Client : response
`);

Input types

CommitGraph

interface Commit {
  id: CommitId;          // unique string
  parents: CommitId[];   // mainline first; [] = root; 2+ = merge
  branch?: string;
  message?: string;      // drives label sizing
  author?: string;
  hash?: string;         // short hash shown mono before the subject
}

interface CommitGraph {
  commits: Commit[];
  refs?: Record<string, CommitId>; // e.g. { main: "a1" }
}

FlowGraph

type NodeShape    = "dot" | "box" | "stadium" | "diamond" | "cylinder";
type SemanticRole = "primary" | "success" | "error" | "warning" | "streamed" | "neutral";
type FlowDirection = "TD" | "LR";

interface FlowNode {
  id: NodeId;
  label?: string;      // \n / <br> → multi-line
  shape?: NodeShape;   // default "box"
  role?: SemanticRole; // controls color
  group?: NodeId;      // subgraph membership (see FlowGraph.groups)
}

interface FlowEdge {
  from: NodeId;
  to: NodeId;
  label?: string;
  colored?: boolean;            // true → inherits source node's role color
  arrow?: "none" | "end" | "both"; // default "end" (directed); "none" = undirected
}

interface FlowGraph {
  kind: "flow";
  nodes: FlowNode[];
  edges: FlowEdge[];
  direction?: FlowDirection;
  groups?: { id: NodeId; label?: string }[]; // subgraph containers
}

SequenceGraph

type MessageKind = "sync" | "async" | "self";

interface SequenceParticipant { id: NodeId; label?: string; role?: SemanticRole; }
interface SequenceMessage { from: NodeId; to: NodeId; label?: string; kind: MessageKind; seq?: number; }
interface SequenceNote { over: NodeId[]; text: string; seq?: number; }
// `seq` is the global event order across messages AND notes (0-based). The
// parser stamps it from source line order so notes interleave on the timeline;
// omit it when building a graph by hand and notes sort after all messages.

interface SequenceGraph {
  kind: "sequence";
  participants: SequenceParticipant[];
  messages: SequenceMessage[];
  notes?: SequenceNote[];
}

BlockGraph

interface BlockCell {
  id: NodeId;
  label?: string;
  shape?: NodeShape;
  role?: SemanticRole;
  span?: number;       // column span, default 1
}

interface BlockGraph {
  kind: "block";
  columns: number;
  cells: BlockCell[];
}

Output: PositionedGraph

Both layout functions return a PositionedGraph. Pass it directly to <Graph /> — you rarely need to inspect the fields.

interface PositionedGraph {
  nodes: PositionedNode[];
  edges: PositionedEdge[];
  width: number;   // total diagram width in px (use for container/viewBox sizing)
  height: number;  // total diagram height in px
  laneCount: number;
  groups?: PositionedGroup[]; // flow subgraph boxes / sequence note boxes
  lifelines?: Lifeline[];     // sequence-diagram lifelines
}

interface PositionedGroup {
  id: NodeId; label?: string;
  x: number; y: number; w: number; h: number; // top-left + size (NOT center)
  variant?: "group" | "note";
}

interface Lifeline { id: NodeId; x1: number; y1: number; x2: number; y2: number; }

width and height include padding and are ready for viewBox="0 0 {width} {height}".


Layout options

Git: LayoutOptions

interface LayoutOptions {
  laneWidth?: number;    // default 28 px
  rowHeight?: number;    // default 40 px (minimum spacing in horizontal mode)
  padding?: number;      // default 16 px
  edgeStyle?: "elbow45" | "orthogonal"; // default "elbow45"
  orientation?: "vertical" | "horizontal"; // default "vertical"
  labelSide?: "left" | "right"; // default "right"
}

In horizontal mode, rowHeight is the minimum inter-commit spacing — commits automatically spread further apart when adjacent label badges would otherwise overlap.

Flow: FlowLayoutOptions

interface FlowLayoutOptions {
  direction?: "TD" | "LR";              // default "TD"
  layerGap?: number;                    // default 56 px — gap between layers
  nodeGap?: number;                     // default 28 px — gap between sibling nodes
  padding?: number;                     // default 24 px — outer padding
  minNodeWidth?: number;                // default 64 px
  nodeHeight?: number;                  // default 36 px
  labelPadX?: number;                   // default 16 px — horizontal padding inside labels
  edgeStyle?: "elbow45" | "orthogonal"; // default "elbow45"
}

Sequence: SequenceLayoutOptions

interface SequenceLayoutOptions {
  columnGap?: number;   // default 120 px — between lifeline columns
  rowGap?: number;      // default 48 px — between message rows
  headerHeight?: number;// default 40 px — participant header box height
  padding?: number;     // default 24 px
  edgeStyle?: "elbow45" | "orthogonal"; // default "orthogonal"
}

Block: BlockLayoutOptions

interface BlockLayoutOptions {
  cellGap?: number;     // default 12 px
  rowHeight?: number;   // default 48 px
  minCellWidth?: number;// default 96 px
  padding?: number;     // default 24 px
}

<Graph /> component

import { Graph } from "@joycostudio/trazo/react";

<Graph
  graph={positionedGraph}
  title="Flowchart" // sets <title> and aria-label on the <svg>
/>
  • Pure function of props — no hooks, no effects, no browser globals.
  • Renders identically as a React Server Component or when client-hydrated.
  • Wraps output in <svg data-slot="trazo-graph">.

Prefer styling via data-slot from CSS (**:data-[slot=group]:…). Inner slots: edge, edge-label, node, label, group (subgraph box), note (sequence note box), group-label, lifelines / lifeline. A narrow type-safe classNames map is also accepted:

interface GraphClassNames {
  edge?: string; node?: string; label?: string; nodeBox?: string; edgeLabel?: string;
  group?: string; groupLabel?: string; lifeline?: string;
}

Theming

Every color the engine emits is a token key (never a literal), resolved through a three-layer CSS-variable chain:

var(--trazo-<slot>, var(--color-<shadcn-token>, var(--<shadcn-token>, <hex>)))
  1. --trazo-<slot> — trazo's own override knob. Unset by default, so it falls through. Set these to re-theme the graph.
  2. --color-<shadcn-token> / --<shadcn-token> — the stock shadcn token the slot adopts by default, so an unthemed graph already matches the consuming app's brand. (In the JOYCO UI kit --primary is the brand blue, so JOYCO apps stay on-brand with zero config.)
  3. <hex> — a last-resort literal, so the SVG is never colorless in an app with no shadcn tokens at all.

To theme the graph, set the --trazo-* variables on any ancestor element. You don't need to know which shadcn token a slot maps to — the slot names are trazo's stable vocabulary.

/* Override knob (default)          → shadcn token it adopts */

/* Flow node roles */
--trazo-primary:   /* → --primary    */
--trazo-success:   /* → --chart-2    */
--trazo-error:     /* → --destructive */
--trazo-warning:   /* → --chart-4    */
--trazo-streamed:  /* → --chart-3    */
--trazo-neutral:   /* → --muted-foreground */
/* Label text on each role box — a -foreground variant of each */
--trazo-primary-foreground: /* … --success-foreground, --error-foreground, … */

/* Git lanes — 6 distinct slots, auto-cycling (mod 6) for extra branches */
--trazo-lane-1:  /* → --primary */
--trazo-lane-2:  /* → --chart-1 */
--trazo-lane-3:  /* → --chart-2 */
--trazo-lane-4:  /* → --chart-3 */
--trazo-lane-5:  /* → --chart-4 */
--trazo-lane-6:  /* → --chart-5 */
/* Label text on each lane chip */
--trazo-lane-1-foreground: /* … --trazo-lane-6-foreground */

/* Page text + strokes (git labels, neutral edges) */
--trazo-foreground:        /* → --foreground */

/* Git label badge accent surface + its text */
--trazo-accent:            /* → --accent */
--trazo-accent-foreground: /* → --accent-foreground */

/* Subgraph container outline + sequence lifelines (a muted gray) */
--trazo-muted-foreground:  /* → --muted-foreground */
/* Sequence note box fill */
--trazo-muted:             /* → --muted */

/* Node "cutout" background — must match the surface behind the SVG */
--trazo-bg:                /* → --background */

Brand-matched by default. Because each slot defaults to a stock shadcn token, dropping trazo into any Tailwind/shadcn app recolors the diagram to that app's palette with no setup. Set the --trazo-* slots only when you want to override that default.

Arrowheads follow their edge. A directed edge's arrowhead is filled with the same token as the edge's stroke, so the --trazo-* role/lane/accent slots recolor heads automatically — there's no separate arrowhead variable.

Surfaces. Subgraph boxes and sequence lifelines use --trazo-muted-foreground for their outline; sequence note boxes fill with --trazo-muted. The git label badge uses --trazo-accent / --trazo-accent-foreground. Override those to restyle the new primitives.

Example — dark branded theme:

// Wrap the <Graph /> in a div that sets the override slots
<div
  style={{
    background: "#0f0f14",
    "--trazo-bg":          "#0f0f14",
    "--trazo-primary":     "#3b82f6",
    "--trazo-success":     "#22c55e",
    "--trazo-error":       "#ef4444",
    "--trazo-warning":     "#f59e0b",
    "--trazo-streamed":    "#a855f7",
    "--trazo-lane-1":      "#3b82f6",
    "--trazo-lane-2":      "#22c55e",
    "--trazo-lane-3":      "#f59e0b",
    "--trazo-accent":      "#3b82f6",
  } as React.CSSProperties}
>
  <Graph graph={graph} title="My diagram" />
</div>

You can also theme via a class instead of inline styles — e.g. className="[--trazo-success:#16a34a]", or a named CSS rule (.trazo-ocean { --trazo-primary: #0369a1; }) on any ancestor.

--trazo-bg is the one slot that must be set explicitly when you change the background — commit circles use it to punch a gap around the center dot so it reads correctly against a colored lane line. (It defaults to --background, so it only needs an explicit value when the graph sits on a non---background surface like a --card/--muted panel.)

On this page