Learn (Almost) Anything
CatalogDownload course

Diagrams as Code with Mermaid

Strategies for diagrams that grow: split into multiple smaller views, use subgraphs and consistent naming, hide detail behind links, and know when a diagram should become two. Then ship them — embed Mermaid in Markdown and on your own site (the ```mermaid fence on GitHub vs. loading mermaid.js and calling mermaid.run() yourself), with a quick note on version pinning so diagrams don't break later.

enMini-course6 lessons1 module5
Diagrams as Code with MermaidMermaid Diagrams in Practicemermaiddiagramscodepractice

Keeping Diagrams Readable and Embedding Them

Strategies for diagrams that grow: split into multiple smaller views, use subgraphs and consistent naming, hide detail behind links, and know when a diagram should become two. Then ship them — embed Mermaid in Markdown and on your own site (the ```mermaid fence on GitHub vs. loading mermaid.js and calling mermaid.run() yourself), with a quick note on version pinning so diagrams don't break later.

Keeping Diagrams Readable and Embedding Them

When a diagram is getting too big

No hard node-count rule exists, but three signals are reliable: the auto-layout produces crossing edges you can't untangle; a new team member spends more than 30 seconds finding the entry point; or you keep adding "context" nodes that don't belong in the diagram's original scope. At that point, reach for one of these moves before fighting the renderer.

Split by audience. An overview diagram introduces the system to someone new; a detail diagram shows one component to someone implementing it. These genuinely serve different readers. Put both in the same Markdown file under separate headings — they complement rather than replace each other.

Split by time horizon. A happy-path diagram and an error-path diagram are easier to read separately than combined with nested alt blocks. The same principle from the sequence diagram submodule applies everywhere.

Split by depth. Draw the top level first — Client → API Gateway → Services. Each service node gets a click directive linking to a second diagram or a fragment anchor in the same doc that expands only that service. Readers drill down on demand; the overview stays clean.

flowchart LR classDef linked fill:#2563eb,stroke:#1d4ed8,color:#fff classDef plain fill:#f1f5f9,stroke:#94a3b8,color:#1e293b U([User]) --> GW[API Gateway]:::plain GW --> Auth["Auth Service ↗"]:::linked GW --> Orders["Order Service ↗"]:::linked GW --> Notif["Notif Service ↗"]:::linked click Auth "#auth-sequence-diagram" "Jump to auth flow" click Orders "#order-sequence-diagram" "Jump to order flow" click Notif "#notification-flow" "Jump to notification flow"
flowchart LR
    classDef linked fill:#2563eb,stroke:#1d4ed8,color:#fff
    classDef plain fill:#f1f5f9,stroke:#94a3b8,color:#1e293b

    U([User]) --> GW[API Gateway]:::plain
    GW --> Auth["Auth Service ↗"]:::linked
    GW --> Orders["Order Service ↗"]:::linked
    GW --> Notif["Notif Service ↗"]:::linked

    click Auth "#auth-sequence-diagram" "Jump to auth flow"
    click Orders "#order-sequence-diagram" "Jump to order flow"
    click Notif "#notification-flow" "Jump to notification flow"
Overview diagram where each service node links to a dedicated detail diagram — the overview stays at five nodes while depth lives elsewhere

Subgraphs as the middle ground

Before splitting into separate files, try a subgraph. A well-named subgraph communicates ownership without multiplying documents. Naming convention matters more than you'd expect — subgraph auth [Auth Service] is immediately clearer than subgraph sg1. Consistent subgraph IDs across diagrams in the same doc make cross-references easier too.

From the flowchart submodule: nesting subgraphs past two levels almost always signals the diagram should split. Subgraphs at depth three start looking like ASCII art — split instead.

One practical habit: if you're copy-pasting the same cluster of nodes into multiple diagrams just to provide context, that cluster wants to be its own named diagram with a stable link. Reference it, don't replicate it.

Consistent naming across diagrams

In a doc with five related diagrams, use the same node IDs for the same concepts throughout. If AuthSvc is a participant in a sequence diagram, use AuthSvc in the flowchart too — not auth, not AuthService, not A. Readers build a mental map; inconsistent naming forces them to re-map on every diagram.

%% ❌ Three names for one concept — reader must re-map every diagram
%% sequence:  participant auth as Auth
%% flowchart: AuthService[Auth] --> GW[Gateway]
%% ER:        Auth ||--o{ Session : "creates"

%% ✓ One stable ID, one display label — reader maps once and reuses
%% sequence:  participant AuthSvc as Auth Service
%% flowchart: AuthSvc[Auth Service] --> GW[API Gateway]
%% ER:        AuthSvc ||--o{ Session : "creates"

Same rule for display labels: pick one canonical name per concept and commit to it. Short source IDs (AS) are fine as long as the display label is always the full readable name (Auth Service).

Hiding detail behind links

This is where the click directive from the previous submodule earns its keep at scale. An architecture overview doesn't need to show every database column or API route — it needs to link to the diagrams that do. With securityLevel: 'loose' set on your site, every node becomes a navigation point.

For a docs site, relative fragment links work cleanly:

click AuthSvc "#authentication-flow" "See auth sequence diagram"

The reader stays in context; the overview stays clean.

The "should this be two diagrams?" test

If you're hesitating, ask: do these two parts of the diagram have different audiences, different update cadences, or different levels of abstraction? Any "yes" is a split signal. A database schema diagram and a call-flow diagram live in the same system but serve completely different readers — keep them separate even if they share entity names.

---

Embedding on GitHub

On GitHub, the triple-fence with the mermaid language tag is all you need:

````

flowchart LR A[User] --> B[API] --> C[(DB)]
flowchart LR
    A[User] --> B[API] --> C[(DB)]

````

GitHub renders this natively in README files, issues, pull request descriptions, and wiki pages — no JavaScript, no CDN link, no configuration. The renderer uses a fixed internal Mermaid version that you don't control, which means diagrams can subtly change behavior across GitHub updates. For stable long-lived diagrams this is usually fine; for precise control, your own site is the answer.

A GitHub repository README showing a Mermaid flowchart rendered natively inside the page — no JavaScript or CDN setup required.

Two GitHub-specific limits: click directives are silently dropped (the same security restriction covered in the previous submodule), and complex diagrams occasionally time out and render as a broken image. When that happens, splitting the diagram is the fix — the timeout is a concrete readability signal, not just a renderer quirk.

Embedding on your own site

The current approach uses an ES module import. The full minimal page:

<!DOCTYPE html>
<html>
<body>

<pre class="mermaid">
flowchart LR
    A[Client] --> B[API] --> C[(Database)]
</pre>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
  mermaid.initialize({ startOnLoad: true });
</script>

</body>
</html>

Three things to know: type="module" is required — the ESM build uses import syntax and won't load without it. Each diagram goes in its own <pre class="mermaid"> block; multiple blocks on the same page all render. And startOnLoad: true tells Mermaid to scan the DOM and render every .mermaid element as soon as the script runs.

mermaid.run() for dynamic content

If diagrams arrive after the initial page load — a single-page app, a lazy-loaded section, content fetched from an API — startOnLoad: true fires too early and misses them. The fix:

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
  mermaid.initialize({ startOnLoad: false });

  // Call this after your dynamic content is in the DOM
  await mermaid.run({
    querySelector: '.mermaid',
  });
</script>

mermaid.run() was added in v10 and is the preferred API for programmatic rendering. It accepts a querySelector to target specific elements. Call it again whenever new diagrams appear — it only processes elements that haven't been rendered yet, so multiple calls are safe.

flowchart TD classDef yes fill:#22c55e,stroke:#16a34a,color:#fff classDef dyn fill:#3b82f6,stroke:#2563eb,color:#fff classDef neu fill:#f1f5f9,stroke:#94a3b8,color:#1e293b Q{"Diagrams present\nin DOM at page load?"} Q -->|"Yes — static HTML"| S["startOnLoad: true"]:::yes Q -->|"No — async or SPA"| F["startOnLoad: false"]:::dyn S --> R["Mermaid auto-renders\nall .mermaid blocks"]:::neu F --> W["Content arrives in DOM"]:::neu W --> RN["await mermaid.run()"]:::dyn RN --> OK["New diagrams render\nAlready-rendered: skipped"]:::neu
flowchart TD
    classDef yes fill:#22c55e,stroke:#16a34a,color:#fff
    classDef dyn fill:#3b82f6,stroke:#2563eb,color:#fff
    classDef neu fill:#f1f5f9,stroke:#94a3b8,color:#1e293b

    Q{"Diagrams present\nin DOM at page load?"}
    Q -->|"Yes — static HTML"| S["startOnLoad: true"]:::yes
    Q -->|"No — async or SPA"| F["startOnLoad: false"]:::dyn
    S --> R["Mermaid auto-renders\nall .mermaid blocks"]:::neu
    F --> W["Content arrives in DOM"]:::neu
    W --> RN["await mermaid.run()"]:::dyn
    RN --> OK["New diagrams render\nAlready-rendered: skipped"]:::neu
Choosing between startOnLoad and mermaid.run() based on when diagram content arrives in the DOM

Version pinning

mermaid@11 in the CDN URL resolves to the latest 11.x release. That gets you patches automatically but also potential layout shifts between minor releases. For production docs, pin to the exact version:

https://cdn.jsdelivr.net/npm/mermaid@11.4.0/dist/mermaid.esm.min.mjs

The current version is always visible on npmjs.com/package/mermaid. When you do update, check the Mermaid changelog before bumping — the rendering engine occasionally changes how it handles edge labels, subgraph direction overrides, or theme variables in ways that shift existing diagrams.

If you use a bundler, install via npm instead:

npm install mermaid@11.4.0

The CDN approach suits documentation sites and small tools well. npm install makes more sense when Mermaid is one of many dependencies in a built project with a lockfile.

---

Quick check

InteractiveEmbedding Quiz
Three multiple-choice questions on embedding Mermaid on your own site. Select an answer to see the explanation, then move to the next question.

1. Take any diagram from an earlier submodule that has more than eight nodes. Split it into an overview (≤5 nodes, each with a click directive linking to a detail section) and a detail view.

2. Build a minimal HTML page that loads Mermaid from jsDelivr with the full version number pinned, sets securityLevel: 'loose', and renders a diagram in a <pre class="mermaid"> block.

3. Add a mermaid.run() call that re-renders a container after a simulated async load — test it by appending a new <pre class="mermaid"> via setTimeout and then calling mermaid.run().

---

That wraps the course. You now have: flowcharts you can debug from scratch, sequence diagrams for real API flows, class and ER diagrams that track schema changes, state machines and Gantt charts for the temporal view, a full styling toolkit with classDef and themeVariables, and a working embedding setup for your own sites. The skill from here is repetition — draw something from your current project and fix what doesn't render cleanly.

Self-check

  1. Your diagram has just over 20 nodes, they're neatly arranged with no crossing edges, and the layout is clean. A colleague new to the project can find the entry point in under 20 seconds. Based on the article, does this diagram require splitting?
  2. You notice you're copy-pasting the same 'Login Service' cluster into several related diagrams just to provide context. What does the article suggest?
  3. In your first flowchart, you name a subgraph `subgraph db [Database Service]`. In a related sequence diagram, you refer to the same logical service but label it `subgraph s1 [DB]` for brevity. What problem does this create?
  4. You're deciding whether to embed diagrams on GitHub in your README or host them on your own documentation site. You want readers to click nodes in the architecture diagram to navigate to detailed flow diagrams. Which platform and approach must you choose?
  5. Your single-page application fetches a diagram from an API and injects it into the DOM two seconds after the page loads. You initialize Mermaid with `startOnLoad: true`. What happens to the newly injected diagram?
  6. You pinned your Mermaid CDN import to version 11.4.0 in your production documentation. Six months later, you notice a new release 11.5.0 is available. What is the main reason the article suggests caution before upgrading?
  7. You have a sequence diagram showing both the happy path (user logs in → token granted → access provided) and the error path (login fails → error shown) using `alt` blocks. According to the article, why might splitting into two separate diagrams be preferable?
  8. You're building a public documentation site with 50 Mermaid diagrams. You need click directives for navigation, want to avoid a build step, and prefer stable rendering with version pinning. Which approach fits best?

Homework

Refactor a Diagram for Readabilitydocument

You're given a 16-node flowchart showing a complete user onboarding process: authentication, profile setup, email verification, payment, and dashboard access — all in one view. It's unclear where a new user starts, the layout has crossing edges, and it's hard to extend. Split this diagram into an overview (4–5 nodes, each representing a major stage) and at least two detail views (one expanded auth flow, one other stage). Link the overview to the details using click directives with markdown anchors. Use consistent node IDs and display labels across all diagrams. Submit the refactored Mermaid code in a markdown file, plus one paragraph explaining why you chose those split points.

Overview diagram contains 4–5 nodes with clear flow and no crossing edges. At least 2 detail diagrams provided. Click directives correctly reference section anchors (e.g., #auth-flow). Node IDs are identical across diagrams when referring to the same concept. Display labels remain consistent for the same components (e.g., 'Auth Service' always shows the same full name). Explanation demonstrates understanding of the split criteria from the article. All Mermaid code is syntactically valid.

Create a Self-Contained Embedded Diagram Pagedocument

Create a complete HTML page that embeds your refactored diagrams from Assignment 1. Include the overview and one detail diagram. Pin the Mermaid version to a specific release (e.g., 11.4.0) using jsDelivr. The page should load in a browser with no build step, and both diagrams should render correctly.

Valid HTML with DOCTYPE declaration. Mermaid version explicitly pinned to a specific minor/patch version (not @11 or @latest). Uses <script type="module"> with correct ESM import syntax. startOnLoad is set to true and configured properly. Each diagram occupies its own <pre class="mermaid"> block. Both diagrams render visually in a browser when the file is opened. Includes at least one code comment explaining why version pinning matters.

Render Diagrams That Arrive After Page Loaddocument

Extend your HTML from Assignment 2 to show how to handle diagrams loaded dynamically. After the page loads, use setTimeout to simulate a 2-second API fetch, then insert a new detail diagram into the DOM and call mermaid.run() to render it.

startOnLoad is set to false in mermaid.initialize() (necessary for dynamic rendering). setTimeout or similar delay simulates async content arrival. New <pre class="mermaid"> block is dynamically created and appended to the DOM. mermaid.run() is called with correct parameters after the diagram HTML is inserted. New diagram renders successfully and appears in the browser. Code comments explain why startOnLoad: false is needed here instead of true. No JavaScript console errors when the page loads and renders the dynamic content.

Submit and get assignments reviewed in the Learn (Almost) Anything app.

Sources

  1. Getting Started | Mermaid
  2. Usage | Mermaid