The diagram renderer built for the AI era.
Most diagram syntax is now machine-generated and imperfect. Flora renders it gracefully instead of failing silently.
LLMs write Mermaid constantly — and get it slightly wrong constantly. Feed this to a strict parser and you get a blank screen:
flowchart TD
A[User request] --> B{Cache hit?}
B -->|yes| C[Return cached]
B -->|no| D[(Postgres]
style D fill:#f9f
D --> E[Query and render]
Strict parsers (Mermaid): Parse error on line 4 ... Expecting 'SQE', got 'PS'. Nothing renders. Your UI shows an error box or nothing at all.
Flora: the four valid lines render as an interactive diagram. The unclosed D[(Postgres] is skipped whole and reported as a structured diagnostic — never reinterpreted as garbage nodes — and the style directive is acknowledged and deliberately ignored:
[
{ "line": 4, "col": 16, "message": "Unterminated () (missing closing ))", "severity": "error" },
{ "line": 5, "col": 3, "message": "'style' ignored: styling directive — Flora handles styling through themes", "severity": "info" }
]Try it live in the playground — diagrams are encoded in the URL, so they're shareable with a link.
npm install @topspinj/floraimport { 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")
);Or with no build step — the CDN bundle registers a <flora-diagram> custom element:
<script src="https://unpkg.com/@topspinj/flora"></script>
<flora-diagram theme="default">
flowchart TD
A[Dashboard] --> B[API]
B --> C[(Database)]
</flora-diagram>Diagrams are interactive by default: scroll to zoom, drag to pan, click a node to highlight its upstream/downstream lineage.
pip install florajsfrom florajs import Diagram
d = Diagram("""
flowchart TD
a[Start] --> b{Decide}
b -->|yes| c([Done])
b -->|no| a
""", theme="sketch")
d # displays interactively in Jupyter
d.to_svg_file("decision.svg") # headless export — embedded V8, no browserThere's also a programmatic Flowchart builder — see the Python docs.
Flora's rule is never blank, never silently wrong. Every line of input lands in one of three tiers:
- Supported — graph structure: nodes and shapes, edges and labels, chaining, subgraphs, direction, comments. Renders faithfully.
- Gracefully ignored — Mermaid presentation/behavior directives (
classDef,class,style,linkStyle,click,%%{init}%%). Recognized, skipped, reported asinfodiagnostics. Flora handles styling through themes and clicks throughonNodeClick. - Rejected loudly — anything the parser can't understand is skipped whole and reported as an
errordiagnostic ({ line, col, message, severity }). It is never guessed into extra nodes. If nothing parses,render()shows an error card, not an empty SVG.
Prefer failing? Pass strict: true to throw a FloraParseError (diagnostics on .warnings) instead of rendering best-effort. The rehype plugin is strict by default so broken diagrams fail your build.
All functions accept the same options (below) and return warnings alongside their result.
| Function | Returns |
|---|---|
render(input, element, options?) |
renders into a DOM element |
toSVGElement(input, options?) |
detached SVGSVGElement |
toSVGString(input, options?) |
SVG markup string (no DOM needed) |
toPNG(input, options?) |
Promise<Blob> |
toAST(input, options?) |
parsed AST, no rendering |
toLayout(input, options?) |
computed node/edge positions |
{
theme: "default" | "tufte" | "digital" | "sketch" | { /* overrides */
background: "#ffffff",
nodeColors: { fill: "#f0f4ff", stroke: "#4f6df5", text: "#1e293b" },
edgeColors: { stroke: "#94a3b8", label: "#64748b" },
fontFamily: "Inter, sans-serif",
fontSize: 14,
nodeRadius: 8,
shadow: true,
},
interactive: true, // zoom, pan, hover, click-to-highlight lineage
strict: false, // throw FloraParseError instead of best-effort
onNodeClick: (nodeId) => {},
onNodeHover: (nodeId) => {},
onHighlight: (nodeId, upstream, downstream) => {},
}| Shape | Syntax | Shape | Syntax | |
|---|---|---|---|---|
| Rectangle | A[text] |
Stadium | A([text]) |
|
| Rounded | A(text) |
Cylinder | A[(text)] |
|
| Diamond | A{text} |
Queue | A[[text]] |
Subgraphs (subgraph Name ... end) render as collapsible groups and nest.
import { Flora } from "@topspinj/flora/react";
<Flora input={source} theme="tufte" onNodeClick={(id) => select(id)} />import rehypeFlora from "@topspinj/flora/rehype";
// turns ```flora / ```mermaid code fences into rendered diagrams; strict by defaultFlora ships a Claude Code plugin — a skill that writes correct Flora syntax, visualizes dbt lineage from a manifest.json, and returns playground share links:
/plugin marketplace add topspinj/florajs
/plugin install flora@florajs
Then: /flora draw the auth flow for my app. The skill uses the open Agent Skills format, so it works with other agents too.
Flora is young and shaped by the people using it. Bugs (a playground link is the perfect repro), feature requests, and general feedback all go through GitHub issues. PRs welcome — the open issues are a good place to start.
MIT
