Learn / Mermaid & the text format

Mermaid & the text format

Diagrams-as-text is how diagrams live in git. Grafloria reads and writes Mermaid-compatible text — and adds the one thing Mermaid cannot express: your hand-arranged positions, in a sidecar that keeps the text valid Mermaid.

Import

import { importDiagramText } from '@grafloria/engine';

const result = importDiagramText(`flowchart LR
  a[Start] --> b{Gate}
  b -->|yes| c[Ship]
  b -->|no| a`);

result.diagram;             // a live DiagramModel — 3 nodes, 3 links
result.diagram.getNodes()[0].getData('label');   // 'Start'
result.diagram.getNodes()[0].type;               // 'flowchart:process' — typed, not generic

Imported nodes carry semantic types (flowchart:process, flowchart:decision, ER entities, class boxes…), so they render with the right glyphs and get type-aware layout — a state machine lays out differently from an entity-relationship diagram.

Supported types — and the honest line

flowchart/graph, erDiagram, classDiagram, stateDiagram/stateDiagram-v2, architecture-beta and block-beta. These six are verified against real Mermaid by a CI oracle — real Mermaid must accept both the text and what Grafloria writes back. architecture-beta's sided lines and block-beta's grid are laid out by the architecture layout. Everything else is refused loudly rather than rendered wrongly:

const r = importDiagramText('sequenceDiagram\n  A->>B: hi');
r.unsupported;   // 'sequenceDiagram' — check this field; there is no silent wrong guess

The full Mermaid compatibility story →

Export — and the sidecar trick

import { exportDiagramText } from '@grafloria/engine';

const text = exportDiagramText(diagram);

The output is valid Mermaid that any Mermaid renderer accepts. Positions, sizes and styling — things Mermaid has no syntax for — ride in a %%grafloria:document comment block at the end. Mermaid ignores comments; Grafloria reads them back. So the round trip is lossless for Grafloria and legible for everyone else: hand-arrange a diagram, export, commit, re-import — your arrangement survives, and GitHub still previews the file.

The look of an AI-drawn diagram

The diagrams Claude and ChatGPT hand-write as SVG — tinted zones with a caption in a corner, a bold name over a muted line, coloured labels riding their lines, a red note — can be written as Mermaid for Grafloria. The body stays Mermaid any renderer accepts: the look is style, classDef and linkStyle, <b>Name</b><br/>subtitle labels (or **Name**, or a markdown string), a <code> subtitle for monospace, and v11's @{ shape: text } for a note. What Mermaid has no word for rides in %%grafloria: comments other renderers ignore.

flowchart LR
  customer["<b>Customer</b><br/>phone or browser"]
  subgraph ours["OUR SIDE · MUST NEVER SEE A CARD"]
    api["<b>Our API</b><br/><code>sherkety-erp-api</code>"]
  end
  note@{ shape: text, label: "if someone swaps the link…" }
  customer -.->|"card typed as a<br/>#quot;bank account#quot;"| api
  classDef box fill:#fff,stroke:#d0d5dd,shadow:none,rx:4
  style ours fill:#f3f4f6,stroke:#d7dbe0,stroke-dasharray:5 4,color:#4b5563,font-weight:bold,letter-spacing:1px
  linkStyle 0 interpolate stepBefore stroke:#cf222e,color:#cf222e,font-weight:bold
  %% exact position and size — a node or a subgraph
  %%grafloria:at customer 20,78 150x292
  %% where a zone's caption sits
  %%grafloria:group ours caption:bottom-left
  %% every edge's default, then one edge's own
  %%grafloria:edge * * label:above
  %%grafloria:edge customer api from:right@240, to:left@54, label:below, via:300 318

from:/to: pin a line's ends to a point along a side (right@36 is 36 px down the right side, left@50% halfway); label: puts the label above or below its line, with no box; via: lists the bends. linkStyle … color: colours the label, interpolate stepBefore draws right angles, and shadow:none makes a flat box. exportDiagramText(diagram, { lossless: false, positions: true }) writes all of it back, so a diagram built on the canvas exports to the same kind of text.

Live: the same diagram from the native API and from Mermaid →

The same look with no coordinates

Pinning every box is exact, and it is geometry by hand. One comment asks for the architecture layout instead: zones become regions on a grid, boxes are sized to their words and aligned in rows, lines run straight where boxes line up and bend in the gutters. What the text then carries is structure and a few relations — none of them a number:

flowchart LR
  %%grafloria:layout architecture
  customer["<b>Customer</b><br/>phone or browser"]
  subgraph hp["HEALTHPAY'S SIDE · CARDS ARE TYPED HERE"]
    direction LR
    page["<b>HealthPay payment page</b><br/>the card is typed here"]
    wallets["<b>HealthPay wallets</b><br/>hold the customer's money"]
  end
  subgraph ours["OUR SIDE · MUST NEVER SEE A CARD"]
    direction LR
    api["<b>Our API</b><br/><code>sherkety-erp-api</code>"]
  end
  fake["<b>Fake card page</b><br/>not HealthPay's"]
  note@{ shape: text, label: "if someone swaps the link, the customer lands here (M1)" }
  customer -->|card number and CVV| page -->|adds money| wallets
  api -->|asks HealthPay to move money| wallets
  customer -.-> fake
  %% the wallets sit ABOVE the API: the line leaves the API's top
  %%grafloria:edge api wallets from:top, to:bottom
  %% the fake page hangs BELOW the customer
  %%grafloria:edge customer fake from:bottom, to:top
  %% a note beside what it warns about
  %%grafloria:near note fake right

direction LR inside a subgraph (Mermaid's own) lays the zone's boxes in a row. A plain side in from:/to: says which way a line leaves — so where its target sits — and near places a note. An exact %%grafloria:at still wins for the box it names. Export writes the relations and the layout back, never the pixels the layout chose.

Live: edit the text, the drawing re-composes →

Live: Mermaid's own architecture-beta and block-beta →

Text and canvas, live

The framework surfaces wire both directions into the live canvas — instance.exportText() / instance.loadText(text) (React/Vue) and exportText() / loadText() on the Angular canvas. loadText reconciles into the existing diagram rather than replacing it — listeners, plugins and selection survive. That's the pattern behind the Monaco side-by-side demos: edit text, watch the canvas; drag the canvas, watch the text.

Live: the Mermaid round-trip demos →

Scope note: the import result also reports bodyEdited and sidecarInvalid so an editor can tell "the human edited the Mermaid body by hand" from "the sidecar is stale" and re-layout only when needed.

Where next

  • Grafloria vs Mermaid — when text-only is enough and when it isn't.
  • Layout — what happens when imported text has no positions.