Learn (Almost) Anything
CatalogDownload course

Diagrams as Code with Mermaid

Take control of how diagrams look. Nudge layout and spacing with direction choices, subgraphs, and link length; apply color with classDef, the style directive, and per-diagram theme settings (including front-matter config and themeVariables). Then wire up interactivity — click a node to open a URL or jump to an anchor — which is where your own-site rendering really pays off.

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

Layout, Styling, Themes, and Clickable Nodes

Take control of how diagrams look. Nudge layout and spacing with direction choices, subgraphs, and link length; apply color with classDef, the style directive, and per-diagram theme settings (including front-matter config and themeVariables). Then wire up interactivity — click a node to open a URL or jump to an anchor — which is where your own-site rendering really pays off.

Layout, Styling, Themes, and Clickable Nodes

Earlier submodules gave you working diagrams. This one makes them look intentional — and, on your own site, makes them interactive.

Nudging layout

Direction is the first lever. TD vs LR you already know. When different sections of the same diagram suit different orientations, individual subgraphs can declare their own:

flowchart TD
    subgraph pipeline [CI Pipeline]
        direction LR
        A[Lint] --> B[Test] --> C[Build] --> D[Deploy]
    end
    subgraph review [Review Flow]
        direction TB
        E[PR Created] --> F{Approved?}
        F -->|yes| G[Merged]
        F -->|no| H[Changes Requested]
    end

Each subgraph renders internally in its own direction while the outer diagram stays TD. One documented limitation: if any node *inside* a subgraph has a direct edge to something *outside* it, the direction override is silently dropped. Route external edges to the subgraph ID — not to a node inside — to keep the override alive.

Link length nudges spacing between nodes. Each extra dash adds one rank of separation:

A --> B          default gap
A ----> B        wider
A -------> B     wider still

This works on dotted and thick edges too — -.-> becomes -..->. Reach for it when a long edge label overlaps a neighboring node, or when two nodes are visually cramped. If layout still looks off, try reordering node declarations in the source — the renderer places nodes in definition order before the algorithm runs.

Three identical A→B node pairs rendered side by side at increasing gap widths, illustrating the visual effect of adding extra dashes to Mermaid arrow syntax.
flowchart TD subgraph pipeline [CI Pipeline] direction LR P1[Lint] --> P2[Test] --> P3[Build] --> P4[Deploy] end subgraph review [Review Flow] direction TB R1[PR Created] --> R2{Approved?} R2 -->|yes| R3[Merged] R2 -->|no| R4[Changes Requested] end
flowchart TD
    subgraph pipeline [CI Pipeline]
        direction LR
        P1[Lint] --> P2[Test] --> P3[Build] --> P4[Deploy]
    end
    subgraph review [Review Flow]
        direction TB
        R1[PR Created] --> R2{Approved?}
        R2 -->|yes| R3[Merged]
        R2 -->|no| R4[Changes Requested]
    end
Two subgraphs with different internal directions: the CI pipeline runs left-to-right while the review flow runs top-to-bottom.

classDef: reusable node styles

classDef is the right tool for applying the same style to multiple nodes. Define a class once, assign it anywhere:

flowchart LR
    classDef success fill:#22c55e,stroke:#16a34a,color:#fff
    classDef error   fill:#ef4444,stroke:#dc2626,color:#fff
    classDef pending fill:#f59e0b,stroke:#d97706,color:#000
    classDef default fill:#f1f5f9,stroke:#94a3b8,color:#1e293b

Apply with the ::: shorthand right on the node declaration — no separate statement needed:

    B -->|valid| C[Process]:::success
    B -->|invalid| D[Reject]:::error
    C --> E[Queued]:::pending

Or assign in bulk: class nodeA,nodeB,nodeC myClass; on its own line. classDef default is a special name that sets the baseline for every node without an explicit class — define it first, then let named classes override it.

Properties that work inside classDef and style: fill, stroke, stroke-width, color (text color), font-weight, font-size, rx, ry (corner radius). One firm rule from the official docs: avoid styling Mermaid nodes via external CSS. Mermaid's internal styles have higher specificity, so external overrides rarely stick. Use classDef, style, and linkStyle exclusively.

flowchart LR classDef success fill:#22c55e,stroke:#16a34a,color:#fff classDef error fill:#ef4444,stroke:#dc2626,color:#fff classDef pending fill:#f59e0b,stroke:#d97706,color:#000 classDef default fill:#f1f5f9,stroke:#94a3b8,color:#1e293b A([Request]) --> B{Auth?} B -->|valid| C[Handler]:::success B -->|expired| D[Refresh]:::pending B -->|invalid| E[Reject]:::error C --> F[(Database)]:::success D --> B style A fill:#3b82f6,stroke:#2563eb,color:#fff linkStyle 0 stroke:#3b82f6,stroke-width:2px linkStyle 3 stroke:#ef4444,stroke-width:2px
flowchart LR
    classDef success fill:#22c55e,stroke:#16a34a,color:#fff
    classDef error fill:#ef4444,stroke:#dc2626,color:#fff
    classDef pending fill:#f59e0b,stroke:#d97706,color:#000
    classDef default fill:#f1f5f9,stroke:#94a3b8,color:#1e293b

    A([Request]) --> B{Auth?}
    B -->|valid| C[Handler]:::success
    B -->|expired| D[Refresh]:::pending
    B -->|invalid| E[Reject]:::error
    C --> F[(Database)]:::success
    D --> B

    style A fill:#3b82f6,stroke:#2563eb,color:#fff
    linkStyle 0 stroke:#3b82f6,stroke-width:2px
    linkStyle 3 stroke:#ef4444,stroke-width:2px
A request flow with a four-class classDef palette, a one-off style override on the entry node, and linkStyle highlighting on the first and error-path edges.

style and linkStyle: targeted overrides

When a single node needs something that doesn't warrant a named class:

style E fill:#7c3aed,stroke:#6d28d9,color:#fff,stroke-dasharray: 5 5

stroke-dasharray: 5 5 draws a dashed border — useful for flagging a "planned but not yet built" component. Accepts the same properties as classDef but applies only to the named node and can't be reused. If you catch yourself writing the same style on two or three nodes, that's the signal to make it a class.

Edges follow the same pattern. linkStyle targets edges by 0-based index in definition order:

flowchart LR
    A --> B --> C
    linkStyle 0 stroke:#ef4444,stroke-width:2px
    linkStyle 1,2 stroke:#94a3b8,stroke-dasharray: 4 2

Tracking indices in a complex diagram gets tedious fast, so save linkStyle for the edges that genuinely need visual priority — a critical path, an error branch, a fallback route.

Themes and per-diagram config

Five built-in themes: default, neutral, dark, forest, base. Pass one to mermaid.initialize({ theme: 'dark' }) on your site and every diagram inherits it globally.

For per-diagram control, put a YAML frontmatter block at the very top of the diagram source:

---
config:
  theme: base
  themeVariables:
    primaryColor: '#1e3a5f'
    primaryTextColor: '#ffffff'
    primaryBorderColor: '#0f2d4f'
    lineColor: '#64748b'
    background: '#f8fafc'
---
flowchart TD
    A[Client] --> B[API] --> C[(Database)]

Two rules that catch most people:

1. Only base is customizable. The other four themes have fixed palettes; themeVariables on them is silently ignored. Always pair theme: base with your variable overrides.

2. Hex colors only. Named colors like cornflowerblue and rgb() values are both silently ignored. Use #6495ed.

primaryColor drives the default node fill. primaryBorderColor auto-adjusts when you change primaryColor, but can be overridden explicitly. For sequence diagrams the key variables are actorBkg and noteBkgColor; for state diagrams, stateBkg. The full list lives in the Mermaid theming docs.

You may encounter %%{init: {"theme": "base"} }%% at the top of diagrams online — that's the old directive syntax, now deprecated. It still parses in most renderers, but when writing new diagrams, use the frontmatter block above.

Click directives: wiring nodes to URLs

Hosted renderers like GitHub disable clicks for security. On your own site you control the initialization — that's what makes this worthwhile.

Step 1 — set securityLevel when initializing Mermaid:

mermaid.initialize({
  securityLevel: 'loose',
});

Step 2 — add click statements at the bottom of the diagram:

flowchart LR
    classDef linked fill:#2563eb,stroke:#1d4ed8,color:#fff

    A[Home] --> B[API Docs] --> C[Reference]

    click A "https://example.com" "Homepage" _blank
    click B "https://example.com/api" "API reference" _blank
    click C href "https://example.com/ref" _self

    class A,B,C linked

Syntax: click nodeId "url" "optional tooltip" target. Targets: _blank (new tab), _self (current tab — the default). The href keyword between node ID and URL is optional. For on-page anchors, a relative fragment works fine: click B "#authentication" scrolls to the matching heading on the same page — a clean pattern for documentation sites where one long page embeds multiple diagrams.

Two gotchas that waste time:

  • If securityLevel is 'strict' (Mermaid's default), click directives are silently dropped. The diagram renders normally; clicks just do nothing, with no error or warning. Setting 'loose' is always the fix.
  • Click directives don't fire in the mermaid.live Live Editor. Test on an actual local HTML page, not the preview pane.
flowchart LR classDef linked fill:#2563eb,stroke:#1d4ed8,color:#fff A[Home] --> B[Docs] --> C[Reference] A --> D[Examples] click A "https://example.com" "Homepage" _blank click B "https://example.com/docs" "Read the docs" _blank click C "https://example.com/ref" "Full reference" _self click D "#examples" "Jump to examples" class A,B,C,D linked
flowchart LR
    classDef linked fill:#2563eb,stroke:#1d4ed8,color:#fff

    A[Home] --> B[Docs] --> C[Reference]
    A --> D[Examples]

    click A "https://example.com" "Homepage" _blank
    click B "https://example.com/docs" "Read the docs" _blank
    click C "https://example.com/ref" "Full reference" _self
    click D "#examples" "Jump to examples"

    class A,B,C,D linked
Click directives wired to URLs and an on-page anchor. Clicks only fire on pages where mermaid.initialize({ securityLevel: 'loose' }) is set — not in this embedded preview.

Keeping styled diagrams maintainable

A few habits that scale:

  • Lock down your palette with classDef. Three or four consistent class names — success, error, warning, info — used across all diagrams in a doc make the whole site feel coherent without extra effort.
  • classDef default as the baseline. Define it first in every diagram; let named classes override it.
  • Keep style and linkStyle to genuine outliers. If the same override appears on three nodes, it belongs in a class.
  • Test click directives on a local page early. Wire up one node, confirm it fires, then add the rest.

Quick check

InteractiveStyling & Theming Quiz
Four multiple-choice questions on classDef, themeVariables, click directives, and linkStyle. Select an answer to see an explanation, then move on.

1. Write a flowchart LR with three classDef classes — distinct hex colors — and at least four nodes styled with :::.

2. Add a classDef default to set the baseline for un-styled nodes.

3. Write a YAML frontmatter block with theme: base and a primaryColor override. Paste it into mermaid.live to confirm it renders.

4. Add two click directives — one _blank, one with a #fragment anchor. Set up a minimal local HTML page with securityLevel: 'loose' and confirm both fire.

The final submodule covers keeping large diagrams readable and embedding them on your own site — including when to reach for mermaid.run() versus letting Mermaid auto-initialize.

Self-check

  1. You want node C (outside a subgraph) to connect to node A (inside a subgraph with `direction LR`). You write `C --> A`. What happens?
  2. Your diagram has a long edge label that overlaps an adjacent node. You add extra dashes to the edge. What does this actually do?
  3. You're styling three error nodes and one special critical node with a dashed border. Which approach is best?
  4. You set `theme: dark` with custom `themeVariables` in YAML frontmatter. The custom colors don't appear. What's wrong?
  5. You wrote `click A 'https://example.com'` and tested it on mermaid.live without success. Your local page has `securityLevel: 'strict'`. Changing it to `'loose'` makes it work. Why?
  6. You want two clickable nodes: one opening a new tab to an external site, another scrolling to a page heading. Which syntax is correct?
  7. You find an old diagram with `%%{init: {"theme": "dark"} }%%` at the top. What should you know about this?
  8. You style Mermaid nodes with an external CSS rule but the colors don't apply. Why?

Homework

Design and style a workflow diagramtext

Build a Mermaid flowchart for a deployment pipeline or support ticket workflow. You'll need at least three `classDef` classes—each with its own hex color—to style different node types. Apply them to at least 6 nodes using `:::`. Include a subgraph that overrides direction (so maybe that section flows left-to-right while everything else goes top-to-bottom). Use extended link syntax (----> instead of -->) once or twice to add spacing. Always define `classDef default` first—it's your baseline for unstyled nodes. Test on mermaid.live to make sure it renders, then submit the complete source code.

• Defines 3+ classDef classes with distinct hex colors • Applies classes to at least 6 nodes using ::: syntax • Includes at least one subgraph with direction override • Uses extended link syntax (4+ dashes) at least once • classDef default is defined first, before other classes • Diagram renders without errors on mermaid.live • Source code is complete and ready to run

Apply theme customization and embed in HTMLarchive

Take your diagram from Assignment 1 and add custom theming. Put a YAML frontmatter block at the very top (--- on its own line, config inside, --- again). Set `theme: base` and include at least two `themeVariables` with hex colors. Remember: only `base` respects theme variables; other themes ignore them. Create an HTML file with Mermaid loaded and your diagram in a `<pre class="mermaid">` block. Open it in a browser, verify your custom colors appear, then submit the HTML file in a .zip.

• YAML frontmatter is syntactically correct (--- delimiters, proper YAML) • Uses `theme: base` (not other built-in themes) • Includes 2+ themeVariables with hex color values • All color values use hex format (#rrggbb) • Diagram is in a <pre class="mermaid"> block • HTML initializes Mermaid with mermaid.initialize() and mermaid.run() • Custom colors are visibly applied when file opens in browser • Archive contains a working, self-contained HTML file

Create an interactive navigation diagramarchive

Build a diagram for a site navigation, feature roadmap, or component map. Add at least 4 click directives: at least 2 pointing to external URLs with target="_blank", and at least 2 pointing to page anchors like #faq or #getting-started. Critical detail: your HTML must initialize Mermaid with `securityLevel: 'loose'`—without it, clicks silently don't work. Add matching anchor points to your HTML (e.g., `<h2 id="faq">FAQ</h2>`) so fragment clicks navigate. Test locally to confirm clicks fire, and leave an HTML comment documenting what you tested. Submit as a .zip.

• HTML initializes Mermaid with `securityLevel: 'loose'` • Diagram contains 4+ click directives • At least 2 click directives use external URLs with target="_blank" • At least 2 click directives use fragment anchors (e.g., #section-name) • HTML includes id attributes matching at least 50% of fragment links • All click statements are syntactically correct • Clicks are tested and functional when file opens locally • File is valid HTML and opens without errors • Archive contains a working, self-contained HTML file

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

Sources

  1. Mermaid Theme Configuration
  2. Mermaid Flowchart Syntax — Styling and Interaction
  3. Mermaid Configuration