Diagrams
Mermaid diagrams as document blocks: flowcharts, sequence diagrams, state and class diagrams, Gantt charts and the rest of Mermaid’s syntax, written as text and rendered as SVG.
| Package | Contents |
|---|---|
@stesura/mermaid | The mermaid node spec, commands, rendering (lazy-loaded), markdown and DOCX support. Framework-free. |
@stesura/mermaid-react | createMermaidUiExtension(): the node view, the source editor, the Insert group and the Diagram tab. |
Unlike callouts, the node is not in the core schema: Mermaid weighs about 2.5 MB, so it stays opt-in, and is only downloaded when the first diagram renders.
mermaid and dompurify are optional peers of @stesura/mermaid, installed with
@stesura/mermaid-react. A server that only needs the node (a collab backend, an export job)
imports @stesura/mermaid and never installs them.
Setup
Four pieces: the node in the schema, the UI extension, the keymap plugin and (optionally) markdown.
import { stesuraSchema } from "@stesura/core/schema";
import { StesuraEditor, useLocalEditor } from "@stesura/editor-react";
import { registerMarkdownExtensions } from "@stesura/markdown";
import { mermaidGlobalAttributes, mermaidMarkdown, mermaidNodes } from "@stesura/mermaid";
import { createMermaidUiExtension, mermaidKeymapPlugin } from "@stesura/mermaid-react";
// Module scope: one schema and one extension for every mount.
export const schema = stesuraSchema({
nodes: mermaidNodes,
globalAttributes: { nodes: mermaidGlobalAttributes },
});
registerMarkdownExtensions(schema, mermaidMarkdown);
const uiExtensions = [createMermaidUiExtension()];
const Editor = () => {
const { editorState, dispatch, pluginFactory } = useLocalEditor({
schema,
extraPlugins: () => [mermaidKeymapPlugin()],
});
return (
<StesuraEditor
state={editorState}
dispatchTransaction={dispatch}
schema={schema}
pluginFactory={pluginFactory}
uiExtensions={uiExtensions}
/>
);
};mermaidGlobalAttributesgrants the nodespacing,spanColumnsand whole-nodetrackChanges, as core block nodes have them.mermaidKeymapPlugin()makes Enter (and F2) on a selected diagram open its source editor. Without it, Enter inserts a paragraph after the diagram.registerMarkdownExtensionsmakes every markdown conversion on this schema keep diagrams, including the AI tools’. See Markdown.
Every mount needs the same schema
A document holding a diagram only loads in an editor whose schema has the node:
- PDF export: build the export page’s editor with the same
schemaand extension, or the diagram is an unknown node at export time. See PDF Export. - Collaboration: the server applies steps against its own schema. Give it
mermaidNodesandmermaidGlobalAttributestoo, or it rejects every step that touches a diagram.
In the editor
- Insert tab → Diagram inserts a starter flowchart, selects it and opens the source editor.
- The source editor opens on a double-click, Enter on a selected diagram, or Diagram tab → Edit diagram. It shows the source and a live preview (300 ms after you stop typing). Closing it, by Close, Escape, Ctrl/⌘+Enter or a click outside, applies the edit as one undoable step.
- The Diagram tab appears while a diagram is selected.
A diagram is an atom: the caret never enters it, and selecting it selects the whole block. Its source is the node’s text content, so it copies, pastes and syncs like text. A source that doesn’t parse shows a localized error in place of the drawing; the source is kept.
Commands
import {
DEFAULT_MERMAID_SOURCE,
insertMermaid,
mermaidSource,
setMermaidSource,
} from "@stesura/mermaid";
import { openSelectedMermaidEditor, requestMermaidEditor } from "@stesura/mermaid-react";| Export | Signature | Behaviour |
|---|---|---|
insertMermaid | (source?) => Command | Inserts a diagram (default DEFAULT_MERMAID_SOURCE) and selects it. False where a block can’t go. |
setMermaidSource | (pos, source) => Command | Replaces the source of the diagram at pos. False when unchanged. |
mermaidSource | (node) => string | The diagram’s current source, leaving out text pending a tracked deletion. |
openSelectedMermaidEditor | Command | Opens the selected diagram’s source editor. As a probe (no dispatch), only reports whether one is selected. |
requestMermaidEditor | (nodeId) => void | Opens a diagram’s source editor by id, from anywhere. |
Pagination and PDF
A diagram is measured as one box (measureAs: "atomic") and never split. The SVG scales down to
the column width, keeping its aspect ratio. One taller than the page shrinks to fit it, down to
half its size: past that it stays at half size, its bottom is cut off and a banner says so. A
diagram that long is better split into several.
Rendering is asynchronous. The node view sets data-render-pending until the SVG or the error
lands, and the PDF export waits for it, then for the resize to reach pagination, before capturing.
Chromium prints the SVG as vectors.
Markdown
mermaidMarkdown maps the node to a fenced code block tagged mermaid, in both directions:
```mermaid
sequenceDiagram
Alice->>Bob: Hello
```Register it once per schema with registerMarkdownExtensions(schema, mermaidMarkdown), or pass
it per call: parseMarkdown(md, { schema, ...mermaidMarkdown }) and
serializeBlocks(blocks, mermaidMarkdown). Without it, a mermaid fence is an ordinary code
block and a diagram serializes to nothing, with a warning.
The same mechanism serves any custom node: a MarkdownExtension is a map of fence handlers (by
info-string language) and node handlers (by node name).
DOCX
Word has no diagram, so a diagram is exported as a picture, and the picture’s description carries the source so import can restore it.
import { downloadDocx, importDocx } from "@stesura/docx";
import { mermaidDocx } from "@stesura/mermaid";
await downloadDocx(state, comments, "report.docx", mermaidDocx);
const { doc } = await importDocx(file, schema, { nodeTransform: mermaidDocx.nodeTransform });With the toolbar buttons, configure the DOCX group instead of using docxUIExtension:
import { createDocxUIExtension } from "@stesura/docx-react";
import { mermaidDocx } from "@stesura/mermaid";
const docxExtension = createDocxUIExtension({
exportOptions: mermaidDocx,
nodeTransform: mermaidDocx.nodeTransform,
});- Export in a browser: each diagram is rendered, rasterized to PNG at twice its size (sharp
in print), and written as a centred picture at its natural size, scaled down to fit the column
and the page’s height. Its description is
mermaid:followed by the source as a JSON string. - Export without a browser (a server, a script): there is nothing to render with, so the diagram is written as its source in a code block, with one warning. A diagram that fails to render in the browser falls back the same way.
- Import: a picture whose description starts with
mermaid:becomes the diagram again. Its image is not uploaded. A diagram edited as a picture in Word comes back with the source it was exported with.
Track changes
A diagram inserted or deleted while tracking is a tracked block, accepted or rejected as a whole. A source edit is tracked text inside it: the drawing shows the proposed source, rejecting restores the old one.
Limitations
- Headers and footers are painted from the node’s
toDOM, outside the editor (see Extending the Schema), so a diagram there shows its source rather than the drawing. - Themes and captions are not supported yet: diagrams render with Mermaid’s default theme.
Security
The source is document data: a collaborator, a pasted page or an imported file can set it to
anything. Rendering is locked down in @stesura/mermaid, not in your code:
- Mermaid runs with
securityLevel: "strict"(no click handlers, HTML in labels encoded) andhtmlLabels: false(labels are SVG text, never embedded HTML). - A source over
MERMAID_SOURCE_MAX(20,000 characters) is refused before Mermaid loads, and a render that takes over 5 seconds is abandoned. Mermaid’s own limits cap the text size and the number of edges. - The SVG passes through DOMPurify’s SVG profile before it reaches the page: scripts, event
handlers,
foreignObjectand external references are removed. - Each render gets its own id, which Mermaid scopes its styles to, so one diagram can’t restyle another or the page.
Next steps
@stesura/mermaidand@stesura/mermaid-react: API reference- Callouts: the other documentation block, built in
- UI Extensions:
toolbarTabs, which the Diagram tab uses