Diagrams as code: Mermaid in Markdown

Keeping documentation in sync with the systems it describes is a constant battle. Text drifts, screenshots age, and diagrams—traditionally rendered as static images—are especially prone to becoming misleading artifacts. Mermaid, a JavaScript-based diagramming tool, offers a different approach: define charts and flowcharts as text, right alongside your Markdown, and let the browser render them as SVG.

This "diagrams-as-code" model fits naturally with static site generators and the Jamstack. Mermaid syntax mirrors the fenced code block convention from Markdown, so a diagram is just another code block with the language set to mermaid. The source stays version-controlled, reviewable, and adjacent to the prose it illustrates. A Gantt chart for a product roadmap, for instance, takes only a few lines:

gantt
  title My Product Roadmap
  dateFormat  YYYY-MM-DD
  section Cool Feature
  A task           :a1, 2022-02-25, 30d
  Another task     :after a1, 20d
  section Rad Feature
  Task in sequence :2022-03-04, 12d
  Task, No. 2      :24d

The result is an SVG rendered inline:

Showing a Mermaid diagram of a roadmap in shades of purple.
Nine lines of code gets us a full-fledged Gantt chart that can be used for product roadmaps and such.

You can experiment with the syntax in Mermaid's live editor at mermaid.live.

From Markdown to Mermaid's DOM target

When a Markdown processor encounters a mermaid fenced code block, it treats it like any other code block and wraps the content in a <pre> element. A standard CommonMark-compliant processor produces output like this:

<pre><code class="language-mermaid">graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;
</code></pre>

Mermaid's API, however, expects the diagram source to live directly inside a <div class="mermaid"> element—without any intermediate <code> or <span> tags, which syntax highlighters often introduce. A small JavaScript snippet bridges the gap, walking the DOM to find <pre> elements, extracting their text content with textContent, and remounting them as Mermaid divs. This method is deliberate: textContent decodes HTML entities (like converting &gt; back to >) and strips stray descendant elements that Markdown conversion might leave behind.

// select <pre class="mermaid"> _and_ <pre><code class="language-mermaid">
document.querySelectorAll("pre.mermaid, pre>code.language-mermaid").forEach($el => {
  // if the second selector got a hit, reference the parent <pre>
  if ($el.tagName === "CODE")
    $el = $el.parentElement
  // put the Mermaid contents in the expected <div class="mermaid">
  // plus keep the original contents in a nice <details>
  $el.outerHTML = `
    <div class="mermaid">${$el.textContent}</div>
    <details>
      <summary>Diagram source</summary>
      <pre>${$el.textContent}</pre>
    </details>
  `
})

Loading and configuring Mermaid

Mermaid ships as an npm package. For a quick setup, use a CDN like unpkg and load the minified bundle, mermaid.min.js, rather than the default mermaid.core.js export:

<script src="https://unpkg.com/[email protected]/dist/mermaid.min.js"></script>

It also supports ESM, so you can import it via Skypack:

<script type="module">
  import mermaid from "https://cdn.skypack.dev/[email protected]";
</script>

If you run the DOM conversion script before loading Mermaid, its default auto-initialization on document ready is sufficient—no further configuration needed. For more control, a custom initialization object is available, and three settings are worth adjusting:

// initialize Mermaid to [1] log errors, [2] have loose security for first-party
// authored diagrams, and [3] respect a preferred dark color scheme
mermaid.initialize({
  logLevel: "error", // [1]
  securityLevel: "loose", // [2]
  theme: (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) ?
    "dark" :
    "default" // [3]
})
  • logLevel controls the verbosity of error and debug output in the console. Increase it while developing to get more insight into rendering issues; drop it for production.
  • securityLevel determines how much trust is placed in the diagram source. The default, "strict", is appropriate for untrusted, user-generated content. If you author the diagrams yourself, "loose" is acceptable.
  • theme switches the visual style of the output. You can pick a palette programmatically—for example, querying the user's preferred color scheme with matchMedia and selecting "dark" with a ternary operator.

A progressive enhancement approach

The strength of this setup is that it degrades gracefully. If JavaScript is disabled or fails to load, visitors still see the original Mermaid source code inside the <pre> block—unstyled but not broken. The diagram simply falls back to its textual representation.

For server-side rendering, Mermaid also provides a command-line interface (mermaid-cli). It could be hooked into a build process to pre-render diagrams to static images, which could then serve as <img> fallbacks instead of the code block.

Native Mermaid support is appearing in more platforms, and the value is clear: complex relationships—decision trees, sequences, timelines—are far easier to grasp visually than in prose. The difference is that these visuals can now be maintained with the same rigor as the text they accompany. Because the source is plain text, reviewing a change to a diagram is as simple as reviewing a change to a sentence. That keeps documentation truthful, not just pretty.