Skip to main content

React Mermaid, no Mermaid.js runtime

@react-markdown-kit/mermaid renders a ```mermaid fence in React Markdown as static SVG, without loading Mermaid.js. For now it handles flowcharts and sequence diagrams. In the editor, the same plugin opens a flowchart on a drawing canvas and writes plain Mermaid back when you're done.

If you just want to try the canvas, it's at reactmarkdownkit.com/mermaid-editor. This page covers the React side: installing it, rendering, server rendering, editing, and the one comment line where positions get stored.

Install​

npm install @react-markdown-kit/renderer @react-markdown-kit/mermaid

The renderer is a peer dependency. The plugin's root entry doesn't depend on anything at runtime and doesn't import React or Lexical. We check that in scripts/pack-check.mjs, which installs the packed tarball into a consumer without Lexical and renders a diagram.

Render a flowchart in nine lines​

import Markdown, { defineMarkdownPreset } from '@react-markdown-kit/renderer'
import { mermaid } from '@react-markdown-kit/mermaid'
import '@react-markdown-kit/mermaid/styles.css' // optional: surface, border, dot grid

const preset = defineMarkdownPreset({ extensions: [mermaid()] })

export function Doc({ content }: { content: string }) {
return <Markdown preset={preset}>{content}</Markdown>
}

mermaid() is the only function you call. It turns each mermaid fence into a diagram node, and the renderer draws flowcharts and sequence diagrams as SVG. Any other Mermaid diagram type is shown as source inside the figure, unless the host adds a fallback that renders it.

The example below is rendered by the plugin on this page. The browser doesn't do any work here, the SVG is already in the HTML the server sent.

Rendered by @react-markdown-kit/mermaid, editable
Markdown (edit me)
Rendered
DiagramRequestCompiled documentReact elementsStatic HTMLparse

Static SVG, no runtime​

The output is one figure holding one svg.

<figure data-rmk-diagram="mermaid">
<svg viewBox="0 0 640 260" role="img" aria-label="Diagram">…</svg>
</figure>
  • Shapes are rect, ellipse, circle, polygon, path and text, grouped in g, with a title and desc. Colours from style lines land as attributes.
  • The output never contains a script, an event attribute, a foreignObject or an external reference. The test "does not execute anything" feeds the renderer a hostile drawing payload and a hostile flowchart and checks that none of those show up.
  • If the parser can't read a flowchart, it's shown as source inside the figure, with data-rmk-diagram-error set and a DIAGRAM_INVALID diagnostic on the compiled document. It doesn't throw and it doesn't hide the content.

Size, measured​

Mermaid.js ships a parser and a layout engine for every diagram type. This plugin only parses the types it supports and does its own layout, so it's smaller. The table is generated by scripts/mermaid-size.mjs and stored in docs/data/mermaid-size.json, measured on 2026-09-20. Gzip is node:zlib gzipSync, level 9, one file at a time. 1 KB is 1024 bytes.

What loadsFilesMinifiedGzipped
@react-markdown-kit/mermaid@0.1.0, root entry152.9 KB18.1 KB
mermaid@12.0.0, entry only129.5 KB11.1 KB
mermaid@12.0.0, entry plus the chunks one flowchart loads28861.3 KB237.1 KB
mermaid@12.0.0, entry plus every chunk1055329.9 KB1572.1 KB

How each row was measured:

  • The plugin row is plugins/mermaid/dist/index.js bundled and minified with esbuild (esbuild 0.27.7: --bundle --minify --format=esm, React and Lexical external), then gzipped.
  • The Mermaid rows are the published package, fetched with npm pack (npm pack mermaid@12.0.0; dist/mermaid.esm.min.mjs and dist/chunks/mermaid.esm.min/*.mjs as published, already minified). Its entry, {size.mermaid.entry.file}, lazy-loads one chunk per diagram type. The flowchart row is the static import closure of the entry, the flowDiagram chunk and the dagre layout chunk it requests. The last row is every chunk in the package. Each row sums the files' sizes, gzipped one file at a time.
  • React is not counted on either side. The renderer package is not counted either; it is the Markdown pipeline the plugin plugs into.

For one flowchart, that works out to 13 times less gzipped JavaScript. To be fair, Mermaid.js does a lot more with those bytes, and if your documents need a diagram type other than a flowchart or a sequence diagram, this plugin won't draw it.

Server rendering​

The SVG is built as hast and never touches the DOM, so the plugin should work anywhere the renderer does. That includes a React server component, SSR, a static build, or a Node service that only compiles or templates the document with compileMarkdown and doesn't install Lexical (scripts/pack-check.mjs).

// app/docs/[slug]/page.tsx, a server component
import { readFile } from 'node:fs/promises'
import Markdown, { defineMarkdownPreset } from '@react-markdown-kit/renderer'
import { mermaid } from '@react-markdown-kit/mermaid'

const preset = defineMarkdownPreset({ extensions: [mermaid()] })

export default async function Page({ params }) {
const content = await readFile(`content/${params.slug}.md`, 'utf8')
return <Markdown preset={preset}>{content}</Markdown>
}

The plugin's own tests render through renderToStaticMarkup, and the diagram on this page was server-rendered by Docusaurus the same way. If you want to precompile a document once per process, the Next.js guide goes through that (Markdown in Next.js).

Edit the diagram on a canvas​

@react-markdown-kit/mermaid/editor is the same extension plus canvas editing. Use it in place of the root entry's mermaid() in an editor preset. This is the only entry that loads React and Lexical.

import { MarkdownEditor } from '@react-markdown-kit/editor'
import { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'
import { mermaid } from '@react-markdown-kit/mermaid/editor'

const preset = defineMarkdownPreset({ extensions: [gfm(), mermaid()] })

<MarkdownEditor preset={preset} value={value} onChange={setValue} />

You get an insert-diagram button in the toolbar. You draw boxes and connectors by dragging, and connectors stick to the boxes they touch and follow them around. Copy as Mermaid copies the same text the fence saves, including the layout comment. Other renderers ignore that last line, and this plugin uses it to put the boxes back where you left them.

Loading editor

If you don't touch a fence, it's written back byte for byte (test). If you edit it, it's written as ```mermaid with the positions on the last line (test).

The layout annotation​

Mermaid has no syntax for where a box sits, how big it is, how an edge bends, or free text on the canvas. The editor stores just those things in one comment on the last line of the fence.

%% rmk-layout v1 {"canvasHeight":180,"nodes":{"a":{"x":24,"y":40,"width":160,"height":90}},"edges":{"a->b":{"routing":"elbow"}}}

%% starts a comment in Mermaid (flowchart syntax, comments), so Mermaid.js and the hosts that render fences with it, such as GitHub, ignore the line and lay the flowchart out their own way (host documentation). This plugin reads it back, so the drawing round-trips without losing anything (round-trip tests). A hand-written flowchart without an annotation just gets auto layout.

With a layout annotation: exact positions and elbow routing
Markdown
```mermaid
flowchart LR
    a["Markdown"]
    b(["React"])
    a -->|render| b
    style a fill:#a5d8ff,stroke:#1971c2
    style b fill:#b2f2bb,stroke:#2f9e44
    %% rmk-layout v1 {"canvasHeight":180,"nodes":{"a":{"x":24,"y":40,"width":160,"height":90,"strokeWidth":2},"b":{"x":330,"y":40,"width":170,"height":90,"strokeWidth":2}},"edges":{"a->b":{"routing":"elbow","elbow":0.5}}}
```
Rendered
DiagramMarkdownReactrender

The Mermaid syntax is still the source of truth for the graph and the colours, and the annotation never repeats them. Edges are keyed by their endpoints (a->b, or a->b#2 for a second edge between the same nodes), so an entry still matches after edits that reorder the statements. Reading fails closed. If the payload has a wrong type, an unknown version or there's a second annotation, the whole thing is rejected, the diagram gets auto layout, and the compile result carries a DIAGRAM_LAYOUT_INVALID warning with the path of the first problem (validation tests). Unknown members are ignored, which leaves room for a later minor revision to add fields.

The format is specified in LAYOUT_ANNOTATION.md, shipped with the package, and LAYOUT_ANNOTATION_JSON_SCHEMA is exported from the root.

Flowcharts and sequence diagrams​

For flowcharts, the plugin supports flowchart or graph in any direction, the bracket shapes ([ ], ( ), ([ ]), (( )), { }, {{ }}, [( )], [[ ]], > ]), edge labels in both spellings, lines, arrows, bidirectional arrows, chains, & groups, subgraph … end (read, but the group isn't drawn), style colours and a front-matter title. classDef, click and linkStyle are ignored.

Sequence diagrams render as static SVG and get their own canvas in the editor, which also writes plain Mermaid back. The supported subset is listed in the plugin reference.

Class, state, Gantt, pie and the other diagram types show up as source inside the figure. You edit them as text and they never open on the canvas (test). The subset is covered by mermaid-parse.test.ts. If your documents need those types, I'd add the plugin's fallback entry and render them with Mermaid.js. The plugin hands any fence it doesn't draw over to the host.

FAQ​

Does React Mermaid need Mermaid.js?

No. @react-markdown-kit/mermaid parses the flowchart subset itself and draws static SVG, so nothing loads Mermaid.js. The plugin is 18.1 KB gzipped; the files Mermaid.js 12.0.0 loads for one flowchart are 237.1 KB gzipped, measured by scripts/mermaid-size.mjs.

Can I render Mermaid in a React server component?

Yes. The SVG is built as hast with no DOM and no browser global, so <Markdown> with the mermaid() extension renders in a server component, during SSR and in a static build. The plugin tests render it with renderToStaticMarkup.

Which Mermaid diagram types does the plugin render?

Flowcharts, written as flowchart or graph in any direction, and sequence diagrams. Class, Gantt, pie and every other Mermaid diagram type is shown as source inside the figure, unless the host adds a fallback that renders it.

Does the layout comment break GitHub or Mermaid.js?

No. Positions are stored in one %% rmk-layout v1 comment, and %% starts a comment in Mermaid (mermaid.js.org/syntax/flowchart.html#comments), so Mermaid.js and the hosts that render fences with it, such as GitHub, ignore the line and draw the flowchart with their own layout.

Next​

Mermaid visual editor · @react-markdown-kit/mermaid on npm · Plugin reference · Mermaid live editor alternative · Flowchart to Mermaid · Fix AI-generated Mermaid · Source on GitHub