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 / TD | Top to bottom (default) |
BT | Bottom to top |
LR | Left to right |
RL | Right 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 |
|---|---|---|
background | string | SVG background color |
nodeColors | NodeColorSet | Default node fill, gradient end, stroke, text |
shapeColors | object | Per-shape overrides: diamond, stadium, rounded, cylinder, queue |
edgeColors | object | stroke, label, labelBackground |
fontFamily | string | Font stack for all text |
fontSize | number | Base font size in pixels |
nodeRadius | number | Border radius for rect nodes |
nodePadding | { x, y } | Horizontal and vertical padding inside nodes |
edgeWidth | number | Stroke width for edges |
shadow | boolean | Enable drop shadow on nodes |
subgraphColors | object | fill, 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).