Documentation

Flora is a fault-tolerant, Mermaid-compatible diagram library that renders beautiful, interactive SVGs. It never throws on bad input — it renders what it can and returns structured warnings for the rest.

Installation

npm install @topspinj/flora

Flora ships as ESM and CJS with full TypeScript types. One dependency (dagre for layout).

Quick Start

import { render } from "@topspinj/flora";

const { warnings } = render(
  `flowchart LR
    A[Start] --> B{Decision}
    B -->|Yes| C[Do thing]
    B -->|No| D[Other thing]`,
  document.getElementById("diagram"),
  { interactive: true }
);

// warnings is an array of { line, col, message }

The render() function parses the input, computes layout, and appends an interactive SVG to the target element. It returns any parse warnings so you can surface them in your UI.

Playground

The easiest way to experiment with Flora is the playground — a browser-based editor with live rendering, theme switching, and lineage highlighting.

# Clone the repo and run locally
git clone https://github.com/topspinj/florajs.git
cd florajs
npm install && npm run build
npx serve .
# Open http://localhost:3000/playground.html

Nodes & Shapes

Define nodes with an ID followed by a shape delimiter. If no shape is specified, the node is a plain rectangle labeled with its ID.

Shape Syntax Example Use case
Rectangle [label] A[My Service] Default / processes
Rounded (label) B(Cache) Intermediate steps
Diamond {label} C{Decision} Conditionals / routing
Stadium ([label]) D([Result]) Terminals / outputs
Cylinder (upright) [(label)] E[(Database)] Databases / storage
Queue (horizontal) [[label]] F[[Queue]] Message queues / Kafka
flowchart LR
  A[Service] --> B(Cache)
  B --> C{Route?}
  C --> D([Result])
  C --> E[(Postgres)]
  C --> F[[SQS]]

Edges

Three edge styles are supported:

Style Syntax Example
Solid --> A --> B
Dotted -.-> A -.-> B
Thick ==> A ==> B

Edge Labels

Add labels between pipe characters:

flowchart LR
  A -->|yes| B
  A -->|no| C

Chains

Chain multiple nodes in a single line:

A --> B --> C --> D

Directions

Set the flow direction after the flowchart keyword:

Value Meaning
TB / TDTop to bottom (default)
BTBottom to top
LRLeft to right
RLRight to left

Subgraphs

Group nodes into labeled containers. Subgraphs can be nested and are collapsible by default.

flowchart TD
  subgraph Backend
    API[API Server] --> DB[(Database)]
    API --> Cache(Redis)
  end
  subgraph Frontend
    App[React App]
  end
  App --> API

Subgraphs render as dashed containers with a label pill. Nested subgraphs are supported.

API Reference

render(input, target, options?)

Parse, layout, and render a diagram into a DOM element. Returns parse warnings.

render(
  input: string,
  target: HTMLElement,
  options?: FloraOptions
): { warnings: ParseWarning[] }

This is the primary API. It handles the full pipeline and attaches an interactive SVG to the target element. Subgraph collapse/expand is wired up automatically.

import { render } from "@topspinj/flora";

const { warnings } = render(`flowchart LR
  A --> B --> C`, document.getElementById("el"));

if (warnings.length) {
  console.warn("Parse warnings:", warnings);
}

toSVGElement(input, options?)

Parse, layout, and return an SVG element without attaching it to the DOM. Useful for server-side rendering or when you want to control where the SVG goes.

toSVGElement(
  input: string,
  options?: FloraOptions
): { svg: SVGSVGElement; warnings: ParseWarning[] }
const { svg, warnings } = toSVGElement(`flowchart LR
  A --> B`);

container.appendChild(svg);

toAST(input)

Parse input and return the abstract syntax tree. No layout or rendering is performed.

toAST(
  input: string
): { ast: DiagramAST; warnings: ParseWarning[] }
const { ast } = toAST(`flowchart LR
  A[Start] --> B{Decision}`);

console.log(ast.nodes);
// [{ id: "A", label: "Start", shape: "rect" },
//  { id: "B", label: "Decision", shape: "diamond" }]

console.log(ast.edges);
// [{ from: "A", to: "B", style: "solid" }]

toLayout(input)

Parse and compute node/edge positions. Returns the layout with x/y coordinates for every node and routed edge points.

toLayout(
  input: string
): { layout: LayoutResult; warnings: ParseWarning[] }
const { layout } = toLayout(`flowchart LR
  A --> B --> C`);

// layout.nodes[0] = { id: "A", x: 90, y: 60, width: 100, height: 50, ... }
// layout.edges[0] = { from: "A", to: "B", points: [{x, y}, ...] }
// layout.width, layout.height — bounding box of the full diagram

Options

All API functions that accept options use the FloraOptions interface:

Option Type Default Description
theme "default" | "tufte" | "digital" | Partial<FloraTheme> "default" Theme preset name or custom theme overrides
interactive boolean true Enable zoom, pan, hover, click, and lineage highlighting
onNodeClick (nodeId: string) => void Called when a node is clicked
onNodeHover (nodeId: string | null) => void Called on mouseenter/mouseleave (null on leave)
onHighlight (nodeId, upstream[], downstream[]) => void Called when lineage highlighting is applied

Theming

Presets

Flora ships with three built-in themes:

default

Clean, colorful. Shape-coded nodes with subtle gradients.

tufte

Minimal, print-inspired. Cream background with muted strokes.

digital

Dark mode. Neon accents on a dark slate background.

render(input, el, { theme: "tufte" });
render(input, el, { theme: "digital" });

Custom Themes

Pass a partial theme object to override specific values. Unspecified properties fall back to the default theme.

render(input, el, {
  theme: {
    background: "#1a1a2e",
    nodeColors: {
      fill: "#16213e",
      fillGradientEnd: "#16213e",
      stroke: "#e94560",
      text: "#eee",
    },
    edgeColors: {
      stroke: "#e94560",
      label: "#aaa",
      labelBackground: "#16213e",
    },
    shadow: true,
  },
});

Theme Reference

Full FloraTheme interface:

Property Type Description
backgroundstringSVG background color
nodeColorsNodeColorSetDefault node fill, gradient end, stroke, text
shapeColorsobjectPer-shape overrides: diamond, stadium, rounded, cylinder, queue
edgeColorsobjectstroke, label, labelBackground
fontFamilystringFont stack for all text
fontSizenumberBase font size in pixels
nodeRadiusnumberBorder radius for rect nodes
nodePadding{ x, y }Horizontal and vertical padding inside nodes
edgeWidthnumberStroke width for edges
shadowbooleanEnable drop shadow on nodes
subgraphColorsobjectfill, stroke, label for subgraph containers

Each NodeColorSet has: fill, fillGradientEnd, stroke, text.

Fault Tolerance

Flora never throws. When it encounters invalid syntax, it skips the broken line, renders everything else, and returns structured warnings with line numbers and column positions.

const { warnings } = render(`flowchart LR
  A[Start] --> B{Decision}
  B -->|Yes| C[Do thing
  B -->
  D[End]`, el);

// Renders A, B, D — skips broken lines
// warnings:
// [
//   { line: 3, col: 18, message: "Unterminated [](missing closing ])" },
//   { line: 4, col: 3,  message: "Dangling arrow with no target node" }
// ]

This makes Flora ideal for AI-generated diagrams, where LLMs often produce slightly broken Mermaid syntax.

What Flora recovers from

  • Unterminated brackets, parens, braces, quotes
  • Dangling arrows with no target
  • Arrows followed by unexpected tokens
  • Unterminated subgraphs (missing end)
  • Unknown diagram types (falls back to flowchart)
  • Unexpected characters (skipped with warning)

Interactivity

When interactive: true (the default), Flora adds:

  • Zoom & pan — scroll to zoom, drag to pan
  • Hover — nodes highlight on mouseover (stroke widens)
  • Click handlers — receive callbacks via onNodeClick
  • Subgraph toggle — click subgraph labels to collapse/expand
  • Lineage highlighting — click a node to highlight its upstream and downstream
render(input, el, {
  onNodeClick: (nodeId) => console.log("Clicked:", nodeId),
  onNodeHover: (nodeId) => console.log("Hover:", nodeId),
  onSubgraphClick: (id) => console.log("Toggled:", id),
});

Set interactive: false to render a static SVG with no event handlers.

Lineage Highlighting

Click any node to highlight its full lineage — all upstream ancestors and downstream descendants. Non-related nodes and edges are dimmed. Click the background or press Escape to clear.

render(input, el, {
  onHighlight: (nodeId, upstream, downstream) => {
    console.log(`${nodeId}: ${upstream.length} upstream, ${downstream.length} downstream`);
  },
});

The onHighlight callback receives the clicked node ID plus arrays of all upstream and downstream node IDs, so you can sync highlights with external UI (sidebars, details panels, etc).