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 hosted playground — a browser-based editor with live preview, theme switching, lineage highlighting, starter examples, and SVG / PNG export. No install required.

Shareable URLs

The playground encodes your diagram source and theme in the URL fragment as you type, so sharing a diagram is just copying the URL (the Share button does this for you). The format is:

https://florajs.dev/playground/#flora:<base64url(deflate(JSON{ code, theme }))>

The fragment is compressed with deflate-raw and never sent to the server, so diagrams stay private to whoever holds the link.

Embedding

The hosted /embed route renders a diagram as a clean, chrome-free page, made for <iframe> embeds on platforms that allow HTML but not custom scripts — Ghost, Substack, Notion, and similar. The playground's Embed button copies a ready-to-paste iframe snippet for the current diagram:

<iframe
  src="https://florajs.dev/embed#flora:<encoded>"
  width="100%" height="300"
  style="border:none;" loading="lazy"
  title="Flora diagram">
</iframe>

The diagram source lives in the URL fragment, so it never hits the server. Two formats are accepted: the playground's compressed #flora:… format (above), or plain URI-encoded Mermaid source for hand-written embeds:

https://florajs.dev/embed#flowchart%20TD%0A%20%20A%20--%3E%20B

Query parameters:

  • themedefault, tufte, digital, or sketch. Overrides the theme encoded in a #flora: fragment.
  • interactive=1 — enables zoom, pan, and lineage highlighting. Off by default so the embed never captures the host page's scroll.

For auto-sizing, the page posts { type: "flora:height", height } to the parent window whenever the rendered size changes. On pages where you control scripts, listen and resize the iframe:

window.addEventListener("message", (e) => {
  if (e.data?.type === "flora:height")
    document.querySelector("iframe.flora").height = e.data.height;
});

On pages where scripts are allowed, you can skip the iframe and render inline with the CDN bundle instead:

<div id="flora-diagram"></div>
<script type="module">
  import { render } from "https://esm.sh/@topspinj/flora";
  render(`flowchart TD ...`, document.getElementById("flora-diagram"), { theme: "default" });
</script>

To run the playground locally instead, clone the repo, run npm install && npm run build, then npx serve . and open /playground.html.

Markdown / Rehype

Flora ships a rehype plugin that transforms fenced code blocks into rendered SVG diagrams at build time. It works with any framework that supports rehype — Astro, Docusaurus, Next.js, and more.

Usage

Write Flora syntax in a fenced code block with the flora or flowchart language tag:

```flora
flowchart LR
  A[Source] --> B{Decision}
  B -->|Yes| C[Deploy]
  B -->|No| D[Fix]
```

The code block is replaced with a rendered SVG at build time — no client-side JavaScript required.

Astro

// astro.config.mjs
import rehypeFlora from "@topspinj/flora/rehype";

export default defineConfig({
  markdown: {
    rehypePlugins: [rehypeFlora],
  },
});

Docusaurus

// docusaurus.config.js
import rehypeFlora from "@topspinj/flora/rehype";

export default {
  presets: [
    ['@docusaurus/preset-classic', {
      docs: { rehypePlugins: [rehypeFlora] },
      blog: { rehypePlugins: [rehypeFlora] },
    }],
  ],
};

Next.js

// With next-mdx-remote or @next/mdx
import rehypeFlora from "@topspinj/flora/rehype";

const mdxOptions = {
  rehypePlugins: [rehypeFlora],
};

Options

Pass options as the second element in the plugin array:

rehypePlugins: [[rehypeFlora, { theme: "tufte" }]]
Option Type Default Description
theme "default" | "tufte" | "digital" | "sketch" "default" Theme preset or custom theme object
className string "flora-diagram" CSS class added to the wrapper <div>
languages string[] ["flora", "flowchart"] Code block languages to match
strict boolean true Throw FloraParseError (failing the build) when a diagram has parse errors or an unsupported type. Set to false to render best-effort.

Strict mode is on by default so a broken diagram fails your build instead of publishing silently wrong output — see Error Handling.

The rendered diagrams are static SVGs. For interactivity (zoom, pan, lineage highlighting), use the render() API on the client side.

React

Flora ships a first-class React component that wraps the core render() API. Import it from @topspinj/flora/react.

Usage

import { Flora } from "@topspinj/flora/react";

function App() {
  return (
    <Flora
      source={`flowchart LR
        A[Start] --> B{Decision}
        B -->|Yes| C[Deploy]
        B -->|No| D[Fix]`}
      theme="default"
      interactive={true}
      onNodeClick={(id) => console.log("Clicked:", id)}
    />
  );
}

The component re-renders automatically when source, theme, or callback props change.

Props

Prop Type Required Description
source string Yes Mermaid-compatible diagram source string
theme ThemePreset | Partial<FloraTheme> No Theme preset name or custom theme overrides
interactive boolean No Enable zoom, pan, hover, click, and lineage highlighting
onNodeClick (nodeId: string) => void No Called when a node is clicked
onNodeHover (nodeId: string | null) => void No Called on mouseenter/mouseleave (null on leave)
onHighlight (nodeId, upstream[], downstream[]) => void No Called when lineage highlighting is applied
onWarnings (warnings: ParseWarning[]) => void No Called after each render with any parse warnings
className string No CSS class name for the container div
style React.CSSProperties No Inline styles for the container div

Handling Warnings

Use the onWarnings callback to surface parse warnings in your UI:

function DiagramWithWarnings({ source }) {
  const [warnings, setWarnings] = useState([]);

  return (
    <div>
      <Flora source={source} onWarnings={setWarnings} />
      {warnings.map((w, i) => (
        <p key={i}>Line {w.line}: {w.message}</p>
      ))}
    </div>
  );
}

CDN / Web Component

Flora ships a single-file browser bundle so you can embed diagrams in any HTML page with zero build step. The bundle exposes the full core API on window.Flora and registers a <flora-diagram> custom element.

Usage

<script src="https://unpkg.com/@topspinj/flora"></script>

<flora-diagram theme="default">
flowchart TD
  A[Dashboard] --> B[API]
  B --> C[(Database)]
</flora-diagram>

The element reads its text content as diagram source and renders into a shadow root, so the source stays in the light DOM. It re-renders automatically when the text content or attributes change (via a MutationObserver).

Attributes

Attribute Values Description
theme default | tufte | digital | sketch Theme preset name. Unknown names fall back to default.
interactive "true" | "false" Zoom, pan, hover, click, and lineage highlighting. On by default, matching render() and the React component; set interactive="false" to disable.

Events

When a render produces parse warnings (skipped lines, unterminated blocks), the element dispatches a bubbling, composed flora-warnings event with detail.warnings set to the same ParseWarning[] the core API returns — the CDN equivalent of the React component's onWarnings prop:

document.addEventListener("flora-warnings", (e) => {
  console.warn(e.detail.warnings); // [{ line, col, message }, ...]
});

window.Flora

Every export from the core package is available on the global, so you can also drive rendering imperatively without the custom element:

Flora.render("flowchart LR\n  A --> B", document.getElementById("diagram"));
Flora.toSVGString("flowchart LR\n  A --> B", { theme: "tufte" });

The global also exposes the element class (Flora.FloraDiagramElement) and Flora.registerFloraDiagram(tagName?) to register the component under a custom tag name. Registration under flora-diagram happens automatically when the script loads.

Python

The florajs package on PyPI brings Flora to Python workflows — Jupyter notebooks, dbt/data-lineage visualization, and headless SVG export. It embeds the JS library, so diagrams look identical everywhere: to_svg() runs the renderer in an embedded V8 engine (no browser or Node.js required), and notebook display is fully interactive.

pip install florajs

Usage

from florajs import Flowchart

fc = Flowchart("LR", theme="tufte")
fc.node("raw", "Raw events", shape="cylinder")
fc.node("clean", "Cleaned", shape="stadium")
fc.edge("raw", "clean", "dbt run")

fc                              # displays interactively in Jupyter
fc.to_svg_file("pipeline.svg")  # headless SVG export

Raw Flora/Mermaid syntax works too, via Diagram:

from florajs import Diagram

Diagram("""
flowchart TD
  a[Start] --> b{Decide}
  b -->|yes| c([Done])
""", theme="sketch")

API

Method Description
Flowchart(direction, theme=, strict=) Programmatic builder; every method below chains
.node(id, label, shape=, href=, tooltip=, target=) Add a node; shapes match the syntax shapes
.edge(source, dest, label, style=, arrow=) Connect nodes (solid | dotted | thick, arrow | open | bidirectional)
.subgraph(id) Context manager: nodes defined inside belong to the group; blocks nest
.link(id, href, tooltip=, target=) Attach a click-through link to a node
Diagram(source, theme=, strict=) A diagram from raw Flora/Mermaid syntax
.to_svg() / .to_svg_file(path) Headless SVG export (embedded V8, no browser)
.to_html() / .to_html_file(path) Standalone interactive HTML document
.source / .warnings Generated Flora syntax / parser diagnostics

Errors and Warnings

The Python interface follows Flora's fault-tolerance contract: skipped lines surface as a Python FloraSyntaxWarning, and strict=True raises FloraParseError instead. Theme presets and override dicts mirror the JS themes.

Claude Code

Flora ships an official Claude Code plugin — a skill that teaches Claude to write correct Flora syntax, choose sensible shapes and layouts, and visualize dbt lineage straight from a manifest.json. Every diagram comes back with a playground share link, so you see the rendered result immediately without installing anything.

Install

/plugin marketplace add topspinj/florajs
/plugin install flora@florajs

Usage

Just describe what you want — or invoke the skill directly with /flora:

/flora draw the auth flow for my app — user logs in, we check credentials, if valid issue a JWT, if not show error, then redirect to dashboard

Claude replies with the diagram and a live link you can open immediately:

flowchart TD
  login([User logs in]) --> check{Credentials valid?}
  check -->|valid| jwt[Issue JWT]
  check -->|invalid| err[Show error]
  jwt --> dash([Redirect to dashboard])

Open this diagram in the playground →

The skill triggers on anything that reads like a diagram request — "map out the auth flow in this codebase", "visualize the lineage in my dbt project", "show me everything downstream of stg_orders" — even without an explicit "diagram" keyword. Claude replies with Flora syntax, a live playground link, and — if your project uses @topspinj/flora or the florajs Python package — runnable integration code. The skill follows the open Agent Skills format, so it also works with other agents that support it.

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
Circle ((label)) D((Start)) Start / end points
Stadium ([label]) E([Result]) Terminals / outputs
Cylinder (upright) [(label)] F[(Database)] Databases / storage
Queue (horizontal) [[label]] G[[Queue]] Message queues / Kafka
flowchart LR
  A[Service] --> B(Cache)
  B --> C{Route?}
  C --> D([Result])
  C --> E[(Postgres)]
  C --> F[[SQS]]

Quoted labels

Wrap a label in double quotes to include characters that would otherwise be read as delimiters, like brackets and parentheses. The quotes are stripped from the rendered label, matching Mermaid: A["uses [square] brackets"] renders as uses [square] brackets. Quotes that don't wrap the whole label are kept literally, and quoted edge labels (-->|"yes"|) work the same way.

Edges

Edges come in three stroke styles, each in directed, open (undirected), and bidirectional form:

Style Directed Open (no arrow) Bidirectional
Solid A --> B A --- B A <--> B
Dotted A -.-> B A -.- B A <-.-> B
Thick A ==> B A === B A <==> B

Open links render with no arrowhead — useful in architecture diagrams where the relationship isn't directional. Bidirectional arrows show two-way communication. Click-to-highlight traverses both kinds in both directions.

Edge Labels

Add labels between pipe characters, or inline between dashes:

flowchart LR
  A -->|yes| B
  A -->|no| C
  A -- maybe --> D
  B -- pairs with --- 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.

ERD Diagrams Beta

Flora supports entity-relationship diagrams using Mermaid-compatible erDiagram syntax. Entities render as table boxes with attribute rows; relationships use crow's foot notation for cardinality.

ERD support is in beta. The syntax and layout are stable, but the API may see minor changes before 1.0.

Basic syntax

erDiagram
  CUSTOMER ||--o ORDER : places
  ORDER ||--| LINE-ITEM : contains

  CUSTOMER {
    int id PK
    string name
    string email FK
  }
  ORDER {
    int orderNumber PK
    date placedAt
  }

Cardinality notation

SymbolMeaning
||Exactly one
|o / o|Zero or one
|{ / }|One or many
o{ / }oZero or many

Relationship types

-- = identifying (solid line)  |  .. = non-identifying (dashed line)

Attribute syntax

ENTITY {
  type    name [PK|FK|UK] ["optional comment"]
}

Entities referenced only in relationships are created implicitly with no attributes.

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[]; error?: Error }

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.

render() never leaves the target element empty. If layout or rendering throws internally, it shows an error-state SVG and returns the caught error instead of propagating it. (With strict: true, parse errors still throw FloraParseError before rendering starts.)

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, options?)

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

toAST(
  input: string,
  options?: { strict?: boolean }
): { 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, options?)

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

toLayout(
  input: string,
  options?: { strict?: boolean }
): { layout: LayoutResult; erdLayout?: ERDLayoutResult; warnings: ParseWarning[] }

For flowchart inputs, layout is populated. For ERD inputs, erdLayout is populated instead.

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" | "sketch" | Partial<FloraTheme> "default" Theme preset name or custom theme overrides
interactive boolean true Enable zoom, pan, hover, click, and lineage highlighting
strict boolean false Throw FloraParseError on parse errors or unsupported types instead of rendering best-effort (see Error Handling)
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 four 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.

sketch

Hand-drawn. Wobbly strokes and pastel fills with comic lettering.

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

Unknown preset names fall back to the default theme everywhere a theme name is accepted — render(), resolveTheme(), the React component, the rehype plugin, and the <flora-diagram> element.

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.

Types

Flora exports all TypeScript types from its main entry point. Import them as needed:

import type {
  FloraOptions, FloraTheme, ThemePreset,
  DiagramAST, FlowchartAST, FlowchartNode, FlowchartEdge,
  LayoutResult, LayoutNode, LayoutEdge,
  ParseWarning, ParseWarningSeverity, ParseResult,
} from "@topspinj/flora";

Core Types

Type Description
FloraOptionsOptions passed to render(), toSVGElement(), etc.
RenderResultReturn type of render(){ warnings, unsupportedType?, error? }
ParseResultReturn type of toAST(){ ast, warnings }
ParseWarningA structured diagnostic: { line, col, message, severity }
ParseWarningSeverity"error" | "info" — see Error Handling
FloraParseErrorError thrown in strict mode, carrying all diagnostics in .warnings
DiagramType"flowchart" | "erd" | "unsupported"
FlowchartDirection"TB" | "TD" | "BT" | "LR" | "RL"
NodeShape"rect" | "rounded" | "diamond" | "circle" | "stadium" | "cylinder" | "queue"
EdgeArrowType"arrow" | "open" | "bidirectional" — arrowhead placement on edges
NodeLink{ url, tooltip?, target? } — a clickable link from a click directive
ThemePreset"default" | "tufte" | "digital" | "sketch"

Parsing Types

These types represent the abstract syntax tree returned by toAST().

DiagramAST

A union type: FlowchartAST | ERDAST | UnsupportedDiagramAST. Narrow via the type discriminant field.

ERDAST

PropertyTypeDescription
type"erd"Discriminant
entitiesERDEntity[]All entities, including implicit ones created from relationships
relationshipsERDRelationship[]All relationships with cardinality and identifying flag

ERDEntity

PropertyTypeDescription
idstringEntity name (e.g. "CUSTOMER")
attributesERDAttribute[]Ordered list of attributes

ERDAttribute

PropertyTypeDescription
typestringData type (e.g. "int", "varchar(255)")
namestringAttribute name
key"PK" | "FK" | "UK" | undefinedKey type annotation
commentstring?Optional inline comment

ERDRelationship

PropertyTypeDescription
fromstringSource entity ID
tostringTarget entity ID
labelstringRelationship label (e.g. "places")
fromCardinalityERDCardinalityCardinality at the source entity
toCardinalityERDCardinalityCardinality at the target entity
identifyingbooleantrue = solid line (--); false = dashed (..)

ERDCardinality

"zero-or-one" | "exactly-one" | "zero-or-many" | "one-or-many" — maps to standard crow's foot notation symbols.

FlowchartAST

PropertyTypeDescription
type"flowchart"Discriminant
directionFlowchartDirectionFlow direction (TB, LR, etc.)
nodesFlowchartNode[]All parsed nodes
edgesFlowchartEdge[]All parsed edges
subgraphsFlowchartSubgraph[]All parsed subgraphs

FlowchartNode

PropertyTypeDescription
idstringUnique node identifier
labelstringDisplay text
shapeNodeShapeVisual shape of the node
linkNodeLink?Clickable link from a click directive

FlowchartEdge

PropertyTypeDescription
fromstringSource node ID
tostringTarget node ID
labelstring?Optional edge label
style"solid" | "dotted" | "thick"Edge line style
arrowTypeEdgeArrowType?Arrowhead placement — "arrow" (default), "open", or "bidirectional"

FlowchartSubgraph

PropertyTypeDescription
idstringUnique subgraph identifier
labelstringDisplay label
nodeIdsstring[]IDs of nodes inside this subgraph
parentIdstring?Parent subgraph ID (for nesting)

UnsupportedDiagramAST

PropertyTypeDescription
type"unsupported"Discriminant
detectedTypestringThe diagram type that was detected but not supported

Layout Types

These types represent computed positions returned by toLayout().

LayoutResult

PropertyTypeDescription
nodesLayoutNode[]Positioned nodes
edgesLayoutEdge[]Routed edges with point arrays
subgraphsLayoutSubgraph[]Positioned subgraph containers
widthnumberBounding box width of the full diagram
heightnumberBounding box height of the full diagram

LayoutNode

PropertyTypeDescription
idstringNode identifier
x, ynumberCenter position
width, heightnumberDimensions
labelstringDisplay text
shapeNodeShapeVisual shape
linkNodeLink?Clickable link from a click directive

LayoutEdge

PropertyTypeDescription
fromstringSource node ID
tostringTarget node ID
labelstring?Optional edge label
style"solid" | "dotted" | "thick"Edge line style
arrowTypeEdgeArrowType?Arrowhead placement — "arrow" (default), "open", or "bidirectional"
pointsArray<{ x: number; y: number }>Routed edge waypoints

LayoutSubgraph

PropertyTypeDescription
idstringSubgraph identifier
labelstringDisplay label
x, ynumberTop-left position
width, heightnumberDimensions
nodeCountnumberNumber of contained nodes
parentIdstring?Parent subgraph ID

ERDLayoutResult

ERD-specific layout returned in toLayout().erdLayout.

PropertyTypeDescription
entitiesERDLayoutEntity[]Positioned entity boxes
relationshipsERDLayoutRelationship[]Routed relationship lines with cardinality
widthnumberBounding box width
heightnumberBounding box height

Theming Types

FloraOptions

PropertyTypeDescription
themeThemePreset | Partial<FloraTheme>Theme preset name or custom overrides
interactivebooleanEnable interactive features
onNodeClick(nodeId: string) => voidNode click callback
onNodeHover(nodeId: string | null) => voidNode hover callback
onHighlight(nodeId, upstream[], downstream[]) => voidLineage highlight callback

FloraTheme

PropertyTypeDescription
backgroundstringSVG background color
nodeColorsNodeColorSetDefault node colors
shapeColorsobjectPer-shape color overrides for diamond, stadium, rounded, cylinder, queue
edgeColors{ stroke, label, labelBackground }Edge rendering colors
fontFamilystringFont stack for all text
fontSizenumberBase font size in pixels
nodeRadiusnumberBorder radius for rect nodes
nodePadding{ x, y }Internal node padding
edgeWidthnumberStroke width for edges
nodeStrokeWidthnumberStroke width for node borders
shadowbooleanEnable drop shadow on nodes
handDrawnbooleanEnable hand-drawn sketch style
subgraphColors{ fill, stroke, label }Subgraph container colors

NodeColorSet

PropertyTypeDescription
fillstringNode fill color
fillGradientEndstringGradient end color (same as fill for flat)
strokestringBorder color
textstringText color

Error Handling & Diagnostics

Flora's contract is never blank, never silently wrong. A line the parser cannot understand is skipped in full and reported as a diagnostic with its line number — it is never reinterpreted as extra nodes. Everything valid still renders.

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

// Renders A, B, D — skips the broken line
// warnings:
// [
//   { line: 3, col: 3, message: "Dangling arrow with no target node — line skipped", severity: "error" }
// ]

Every diagnostic has a severity:

  • "error" — the input could not be understood; the affected line was skipped and the rendered diagram may be missing it.
  • "info" — the input was understood and deliberately ignored (see below).

If nothing parses, render() shows an error card listing the first few diagnostics instead of a blank SVG. This makes Flora ideal for AI-generated diagrams, where LLMs often produce slightly broken Mermaid syntax — the mistake is always visible, never silent.

Ignored Mermaid directives

Mermaid's styling and behavior directives solve problems Flora handles natively, so they are recognized and skipped with an info diagnostic rather than misparsed: classDef, class, style, linkStyle (use themes instead), click (use onNodeClick), %%{init}%% directives, and direction inside subgraphs. Pasting Mermaid that uses them renders the same graph, styled by your Flora theme.

Unknown diagram types

Known Mermaid types Flora doesn't support yet (sequenceDiagram, gantt, …) and unrecognized single-word headers render an “unsupported diagram type” notice instead of being guessed at. A headerless fragment like A --> B is treated as a flowchart with an info diagnostic suggesting a flowchart TD header.

Strict mode

Pass strict: true to any API function to throw a FloraParseError on error-severity diagnostics or unsupported types instead of rendering best-effort. Use it wherever nobody is watching the output — static site builds, CI, server-side rendering.

import { toSVGString, FloraParseError } from "@topspinj/flora";

try {
  const { svg } = toSVGString(source, { strict: true });
} catch (e) {
  if (e instanceof FloraParseError) console.error(e.message, e.warnings);
}

The rehype plugin is strict by default: a broken diagram fails the build rather than publishing a wrong one. Pass strict: false to render best-effort. Interactive surfaces like the playground stay lenient and show diagnostics alongside the live render.

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

Feedback

Flora is young and shaped by the people using it. If something rendered wrong, the docs left you guessing, or you're missing a piece of Mermaid syntax, we want to know — all of it goes through GitHub issues:

Pull requests are welcome too — check the open issues for a place to start.