🧩 A lightweight ProseMirror plugin that renders Mermaid diagrams directly inside the editor — with live updates, caching, and tight SVG cropping.
- Live rendering — Mermaid diagrams update automatically as you type.
- Debounced updates — Smooth, performant re-rendering (default: 300 ms).
- Smart caching — Re-renders only when the diagram source changes.
- Syntax highlighting — Works seamlessly with
lowlightandlowlight-mermaid. - Tight SVG cropping — Uses
@svg-fns/layoutfor clean, whitespace-free output. - Robust architecture — Follows ProseMirror best practices via widget decorations and plugin-managed lifecycle.
pnpm add prosemirror-mermaidor
npm install prosemirror-mermaidor
yarn add prosemirror-mermaidimport { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { mermaidPlugin } from "prosemirror-mermaid";
import mermaid from "mermaid";
// Important: initialize Mermaid yourself
mermaid.initialize({ startOnLoad: false });
const state = EditorState.create({
schema,
plugins: [
// ... other plugins
mermaidPlugin({ name: "codeBlock" }),
],
});
const view = new EditorView(document.querySelector("#editor"), { state });| Option | Type | Default | Description |
|---|---|---|---|
name |
string |
'codeBlock' |
Node type treated as Mermaid block. |
lowlight |
ReturnType<typeof createLowlight> |
— | Enables syntax highlighting. Registers mermaid, mmd, and mindmap grammars automatically. |
debounce |
number |
300 |
Delay (ms) before re-rendering after edits. |
mermaidConfig |
MermaidConfig |
— | Pass directly to mermaid.initialize(). |
classList |
string[] | string |
— | CSS classes applied to each diagram container. |
Your ProseMirror schema’s Mermaid node (usually codeBlock) must include:
language: must be"mermaid","mmd", or"mindmap".id: unique identifier (e.g.,m1234abcd).
This is typically managed by your editor or nodeView logic.
If you’re using tiptap, you can generate stable IDs via an addAttributes() extension override.
import { CodeBlockLowlight } from "@tiptap/extension-code-block-lowlight";
import { createLowlight } from "lowlight";
import { mermaidPlugin } from "prosemirror-mermaid";
import mermaid from "mermaid";
const lowlight = createLowlight();
mermaid.initialize({ startOnLoad: false });
const editor = useEditor({
extensions: [
CodeBlockLowlight.configure({ lowlight }).extend({
addAttributes() {
const parentAttrs = this.parent?.() ?? {};
return {
...parentAttrs,
id: {
default: () => `m${crypto.randomUUID().slice(0, 8)}`,
parseHTML: element =>
element.getAttribute("data-id") || `m${crypto.randomUUID().slice(0, 8)}`,
renderHTML: attributes => {
if (!attributes.id) return {};
return { "data-id": attributes.id };
},
},
};
},
addProseMirrorPlugins() {
return [
...(this.parent?.() || []),
mermaidPlugin({
name: this.name,
lowlight: this.options.lowlight,
classList: "mermaid",
}),
];
},
}),
],
});- Uses
Decoration.widgetto inject rendered SVGs after code blocks. - Maintains per-node render cache, debounce timers, and code cache.
- Relies on
@svg-fns/iofor safe SVG parsing andtightlyCropSvgfor layout cleanup. - Handles render errors gracefully with inline messages.
.mermaid-container {
display: flex;
justify-content: center;
padding: 0.5rem;
overflow-x: auto;
background: var(--code-bg, #fafafa);
border-radius: 0.5rem;
}- Mermaid for the visualization engine
- ProseMirror for the editing framework
- @svg-fns for SVG utilities
- lowlight-mermaid for syntax support
This library is licensed under the MPL-2.0 open-source license.
Please enroll in our courses or sponsor our work.
with 💖 by Mayank Kumar Chaudhari
