React Mermaid, no Mermaid.js runtime
@react-markdown-kit/mermaid renders a ```mermaid fence in React Markdown as
static SVG. It does not load Mermaid.js. It renders flowcharts only. The same plugin
opens the fence on a drawing canvas in the editor and writes plain Mermaid back.
Try the canvas at mermaid.reactmarkdownkit.com. This page is for the React side: install, render, server-render, edit, and the one comment that stores positions.
Install
npm install @react-markdown-kit/renderer @react-markdown-kit/mermaid
The renderer is a peer. The plugin's root entry has no runtime dependency, no React
import and no Lexical import, which
scripts/pack-check.mjs proves by
installing the packed tarball into a consumer without Lexical and rendering 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 whole API. It turns a mermaid fence whose body is a flowchart into
a diagram node, and the renderer draws that node as SVG. Every other fence, and every
Mermaid diagram that is not a flowchart, stays an ordinary code block.
The example below is rendered by the plugin, on this page, with no browser work: the SVG is in the HTML the server sent.
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,pathandtext, grouped ing, with atitleanddesc. Colours fromstylelines land as attributes. - No
script, no event attribute, noforeignObject, no external reference. The test "does not execute anything" feeds the renderer a hostile drawing payload and a hostile flowchart and asserts none of them appear. - A flowchart the parser cannot read is shown as source inside the figure, with
data-rmk-diagram-errorset and aDIAGRAM_INVALIDdiagnostic on the compiled document. Nothing is hidden and nothing throws.
Size, measured
Mermaid.js ships a parser and a layout engine for every diagram type. This plugin parses
one type and lays it out itself, so it is 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 loads | Files | Minified | Gzipped |
|---|---|---|---|
| @react-markdown-kit/mermaid@0.1.0, root entry | 1 | 52.9 KB | 18.1 KB |
| mermaid@12.0.0, entry only | 1 | 29.5 KB | 11.1 KB |
| mermaid@12.0.0, entry plus the chunks one flowchart loads | 28 | 861.3 KB | 237.1 KB |
| mermaid@12.0.0, entry plus every chunk | 105 | 5329.9 KB | 1572.1 KB |
How each row was measured:
- The plugin row is
plugins/mermaid/dist/index.jsbundled 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, theflowDiagramchunk and thedagrelayout 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 is 13 times less gzipped JavaScript. Mermaid.js does far more with its bytes. If the document needs a sequence diagram, this plugin will not draw it.
Server rendering
The SVG is built as hast, with no DOM, so the plugin works wherever the renderer works:
a React server component, SSR, a static build, or a Node service that only compiles or
templates the document with compileMarkdown and installs no 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. The Next.js
guide covers precompiling a document once per process:
Markdown in Next.js.
Edit the diagram on a canvas
@react-markdown-kit/mermaid/editor is the same extension with one more capability. It
replaces mermaid() from the root entry in an editor preset, and only this entry 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} />
The toolbar gains an insert-diagram button. Boxes and connectors are drawn by dragging. Connectors bind to the boxes they touch and follow them. Copy as Mermaid copies the same text the fence saves, layout comment included, so other renderers ignore the last line and this plugin restores the positions.
An untouched fence is written back byte for byte
(test).
An edited one is 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 exactly that remainder 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 loss
(round-trip tests).
A hand-written flowchart with no annotation is auto-laid out.
```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}}}
```
The syntax stays authoritative for the graph and the colours. The annotation never
repeats them. Edges are keyed by their endpoints (a->b, a->b#2 for a second edge
between the same nodes), so an entry survives edits that reorder the statements.
Reading is fail-closed: a payload with a wrong type, an unknown version or a second
annotation is rejected as a whole, the diagram is auto-laid out, and the compile result
carries a DIAGRAM_LAYOUT_INVALID warning with the path of the first problem
(validation tests).
Members the reader does not know are ignored, so a later minor revision can 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 only
The plugin renders flowcharts only. flowchart or graph, any direction, the bracket
shapes ([ ], ( ), ([ ]), (( )), { }, {{ }}, [( )], [[ ]], > ]), edge
labels in both spellings, lines, arrows, bidirectional arrows, chains, & groups,
subgraph … end read through, style colours and a front-matter title. classDef,
click and linkStyle are ignored.
Sequence, class, state, Gantt, pie and every other diagram type stay ordinary code
blocks: rendered as source, edited as text, never opened on the canvas
(test). The
subset is covered by
mermaid-parse.test.ts.
If a document needs those types, render them with Mermaid.js; the two can share a page,
since this plugin leaves the fences it does not read untouched.
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 only, written as flowchart or graph in any direction. Sequence, class, Gantt, pie and every other Mermaid diagram type stay ordinary code blocks, rendered as source.
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