Mermaid text, on a canvas you can actually edit
Mermaid renders text to a picture. Grafloria reads that same text into a live diagram you can drag, connect, restyle and delete from — and writes it back out as a body any Mermaid renderer accepts. The text stays the document; the canvas stops being read-only.
This is the real engine parsing real Mermaid in your browser — not a screenshot, not a
pre-baked SVG. Try changing a shape to {Decision?}, adding
-->|label|, or breaking the syntax on purpose.
Six diagram types, read and written
Mermaid has grown past 29 diagram types. Grafloria speaks six of them — the graph-family ones people actually paste into an editor — and every one is a matched parser + model + generator, because a type that parses but cannot re-emit itself loses everything on first save. The rest fail safe (below), which we consider far better than a plausible-looking wrong diagram.
flowchart / graph
The full base grammar plus Mermaid-native styling: all node shapes (a database is a
cylinder, trapezoids and parallelograms are what their slashes say), glued and spaced
edges, edge labels in both spellings (-->|text| and -- text -->),
subgraph → real groups with their own direction, style,
classDef, ::: and click.
architecture-beta
Groups (nested with in), services with their icons, junctions, and lines that
name their sides — db:L -- R:server puts the server to the database's left.
Those sides ARE the layout: groups become regions, services line up, lines run straight.
Arrows at either end, -[label]-, {group} edges.
block-beta
The grid: columns N, blocks filling rows, :N spans,
space holes, block … end nesting a grid of its own, block arrows,
every flowchart shape, edges, style and classDef — each block
filling its cell, lines straight between neighbours.
erDiagram
Entities with typed attribute blocks, PK/FK/UK badges, all sixteen cardinality pairs mapped to crow's-foot markers, identifying vs non-identifying, labelled relationships — landing as interactive table cards, not boxes.
classDiagram
Classes, members in both spellings, visibility and classifiers, generics, annotations, multiplicity, notes — and all 72 legal relationship operators, derived from the grammar rather than a hand-written table.
stateDiagram / -v2
States, transitions, scoped [*] and [H] pseudo-states,
composite states as nested groups, concurrent -- regions, fork / join /
choice, and direction scoped to the composite that declares it.
sequenceDiagram, gantt, pie, …
Every other type — journey, gitGraph, mindmap, timeline, C4, sankey, xychart and the
rest — returns an explicit unsupported signal naming the type. Never a blank
canvas, never a garbage graph. sequenceDiagram is the most-requested next.
It round-trips, and the round trip is the hard part
Reading Mermaid is the easy direction. The interesting one is what happens after somebody moves a node: positions are not expressible in Mermaid's grammar, so a naive exporter throws them away, and every save quietly re-scrambles the diagram.
The visible body always stays valid Mermaid
Grafloria writes a body GitHub, GitLab and mermaid.live all render — and parks everything
the grammar cannot say in a %% comment those renderers ignore. So the same
text is both a lossless Grafloria document and an ordinary Mermaid snippet. That
rule is not a convention we try to remember; it is the constraint the three tiers below
exist to enforce.
Mermaid's own extension points
Anything with a native analog uses it: fill and stroke become style /
classDef, containers become subgraph, navigation becomes
click. Visible, portable, renders everywhere.
%%grafloria: directives
Grafloria-only semantics with no Mermaid analog — edge animation, node status, routing hints — ride in comments, one line per entity. Mermaid skips them; the body still renders.
The document sidecar
A single %%grafloria:document line carries the exact model for a
byte-lossless machine round trip, with a body hash so a hand-edit to the visible text
wins over the stale sidecar.
// npm i @grafloria/engine import { importDiagramText, exportDiagramText } from '@grafloria/engine'; // Mermaid in → a real DiagramModel you can render and edit. const r = importDiagramText(`flowchart TD Start([Start]) --> Check{Valid?} Check -->|yes| Save[[Persist]]`); if (r.unsupported) console.warn(`no parser for ${r.unsupported} yet`); else render({ nodes: r.diagram.getNodes(), edges: r.diagram.getLinks() }, host); // …and back out. lossless:false gives the plain body for a README; // the default adds the sidecar so positions survive the trip. const text = exportDiagramText(diagram, { lossless: false });
Verified against real Mermaid, in CI
A compatibility claim is worth exactly as much as the thing that checks it. Ours is an oracle gate: 28 cases driven through real mermaid v11 in both directions — every input we say we read, and every body we emit, parsed by Mermaid itself on each run. It has already caught an export bug no unit test would have (ER relationship labels colliding with cardinality keywords). Any new type or syntax has to arrive with oracle cases.
The demos are gates too: the Mermaid viewer and the lossless text form both run in CI with real pointer events and pixel-diffed goldens, so the pages on this site cannot rot without failing the build.
What it does not do yet
The gaps below are all lossy, never wrong — the diagram you get is the diagram you wrote, minus something named here. That property matters more to us than the length of the list, so here is the list.
architecture-beta three known losses
- Iconify icons (
logos:aws-lambda) are kept and written back, but drawn with no icon: Grafloria draws the five built-ins (cloud, database, disk, internet, server) and fetches nothing. - A
{group}edge is kept and written back, but it attaches to the service, not to the edge of its group. - The picture is Grafloria's composition, not Mermaid's force layout — the same relations, a different (straighter) arrangement. That one is by design.
block-beta two known losses
- A block arrow draws as its label and an arrow glyph pointing its way, not as an arrow-shaped block; it writes back as the block arrow it was.
- Styles changed on the canvas are not written back — the
style/classDeflines that come out are the ones that went in.
erDiagram three known losses
ENTITY["display alias"]parses, but Mermaid 11 has no syntax to write it back — the alias rides the sidecar instead of the visible body.- ER
style/classDef/:::directives are ignored, not applied to the entity cards. - Relationships are table-level. Mermaid's ER grammar cannot say which column a relationship uses, so the kit's column-pinned edges have nothing to import from. That one is Mermaid's limit, not ours.
classDiagram grouping and two glyphs
namespace Foo { … }— the classes survive, the grouping does not. The largest remaining class gap.- Both-ends operators (
<|--|>,*--o, …) keep their structure and re-export exactly, but draw with one marker: the notation table has no both-ends kind yet. - Lollipop
()--parses and round-trips as a realization, but renders as the dashed triangle rather than the interface ball.
stateDiagram layout, not structure
- Concurrent regions are drawn as dashed bands stacked top to bottom, where Mermaid sets them side by side.
[H]/[H*]round-trip, but draw as a plain circle instead of the H-in-circle glyph.- State
classDef/classstyling is applied on import but not written back on export.
all types layout and the directive channel
- Imported flowcharts and state diagrams lay out as Mermaid lays them out: every edge label gets room between the ranks, and every box grows to hold its text. ER and class diagrams place by dependency (ER by foreign key, class by inheritance depth).
- The Tier-2
%%grafloria:per-entity channel is wired for flowchart only; the other three fall back to the Tier-3 sidecar, which is lossless but opaque.
Mermaid and Mermaid Chart are trademarks of their respective owners; Grafloria is an independent MIT project and implements the Mermaid syntax with its own parser — it does not bundle, fork, or depend on the Mermaid runtime. Coverage described here is current as of October 2026 and is tracked in the engine's gap analysis; corrections and requests are welcome via GitHub issues.