Mermaid Block
A mermaid diagram drawn in the browser — lazily loaded, strictly configured, in token colours — and the Diagram block view for TextEdit and MarkdownView.
- Status
- Since
0.25.39- Accessibility pattern
- figure named by accTitle, status line on an error
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Mermaid Block before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify mermaid-blockInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/mermaid-blockRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/skeleton.
It also adds the sanctioned engine to your package.json: mermaid (diagram renderer (lazily loaded)).
Usage
import { MarkdownView } from "@/components/ui/markdown-view";
import { MermaidBlock, mermaidBlockView } from "@/components/ui/mermaid-block";
import { TextEdit } from "@/components/ui/text-edit";
// A ```mermaid fence in a page, read or edited, is a Diagram block:
<TextEdit
format="markdown"
value={page.body}
onCommit={save}
blockViews={{ mermaid: mermaidBlockView }}
/>;
<MarkdownView blockViews={{ mermaid: mermaidBlockView }}>
{page.body}
</MarkdownView>;
// A diagram on its own:
<MermaidBlock source={source} />;MermaidBlock draws a mermaid source as a diagram, in the browser. The
engine (mermaid 12.1.0) loads in its own chunks the first time a diagram comes within 200px of the
viewport, and draws after the page's fonts are ready, so labels measure right. Colours are the
design tokens of the scheme around it — light, or dark under .dark — re-drawn when it changes;
writers can colour flowchart nodes with class A start|step|decision|outcome|stop|muted. The
diagram is drawn at its natural size, or scaled down to fit its column (never up). One that would
fall below a legible size — 0.6 of its drawn size, about 10px labels — stays at that size and
scrolls sideways inside its box instead, its overflowing edge faded as a table's is, and the box is
then a named region the keyboard can scroll. Expand always shows it whole, to pan and zoom. It prints at most the page's width, never split across pages, in
the light palette. Mermaid lays each diagram out in an off-screen box of its own, so a print
stylesheet that hides the rest of the page never leaves the labels unmeasured.
The usual home is a page: mermaidBlockView is the ready blockViews entry for TextEdit and
MarkdownView, so a ```mermaid fence becomes a Diagram block with Preview / Code,
Expand (full screen, with pan and pinch) and a ⋯ menu — Copy source, Copy SVG, Download SVG. The
document keeps an ordinary fenced code block, so exports, search and agents read the source.
Examples
Errors and other diagram types
Every diagram type mermaid draws works. When a source stops parsing, the last diagram that worked stays, dimmed, with one calm status line: "This change can't be drawn — check line 3. Showing the last diagram that worked." With nothing to keep, the block says "Couldn't draw this diagram — check line 3." and shows the source. The parser's own words ("Expected '-->'.") sit behind Details. A browser that cannot run the engine (Safari before 17.4) gets the source too.
In a page you edit
Code shows the source; on a block at least 720px wide, beside a live preview that redraws 400ms
after you stop typing. ⌘/Ctrl+Enter or Escape goes back to the preview. /diagram (or Turn into ›
Diagram) inserts a new one, which opens in Code with a small flowchart to start from.
In a page you read
On the server, and until the page hydrates, the fence shows its source in the same frame with the diagram's height reserved; the height a diagram last drew at is remembered per source, so a returning reader sees no jump at all.
Rendering without the component
renderMermaid(source, { theme }) resolves to { svg } or { error: { line?, message } } and
never throws; useMermaidSvg(source, { debounce: 400 }) is the hook the block uses — the last SVG
that worked, the current error and whether a drawing is pending, with stale results dropped.
import { renderMermaid } from "@/components/ui/mermaid-block";
const result = await renderMermaid(source, { theme: "light" });
if ("svg" in result) download(result.svg);
else console.warn(`Line ${result.error.line}: ${result.error.message}`);Every render pins the same configuration, inside one queue so two themes never race:
securityLevel: "strict" (a diagram's own %%{init}%% cannot change it, and mermaid sanitises its
SVG), SVG labels only (no HTML, so click links and scripts stay off and the ELK layout engine is
never downloaded), the classic look on the dagre layout, no error diagram, and at most 20,000
characters and 300 edges. SVGs are cached (the last 50, by engine version, theme and source), and
the other scheme is drawn while the page is idle, so a theme switch or a print is instant.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
source* | string | — | The diagram's mermaid source — a ```mermaid fence's body. |
debounce | number | 0 | Milliseconds of quiet after source changes before re-drawing (live editing passes 400). |
emptyMessage | string | "Nothing to draw yet" | What a diagram with nothing to draw says — an empty source, or only %% comments (a new
diagram's starter). |
theme | MermaidTheme | — | The colour scheme to draw in. Omitted, it follows the page (.dark on an ancestor) and
re-draws when that changes. A dark drawing prints its light copy, drawn while the page is idle. |
Data attributes and CSS variables on MermaidBlock
| Attribute | Values |
|---|---|
data-slot | "mermaid-block" | "mermaid-block-detail" | "mermaid-block-print" | "mermaid-block-scroll" | "mermaid-block-skeleton" | "mermaid-block-source" | "mermaid-block-status" | "mermaid-block-svg" |
data-state | "empty" |
| Prop | Type | Default | Description |
|---|---|---|---|
debounce | number | 400 | Milliseconds to wait after the source last changed before drawing it again (while typing). The first drawing never waits. |
enabled | boolean | true | false holds off drawing (the block is far from the viewport). |
theme | MermaidTheme | "light" | The colour scheme to draw in. |
| Prop | Type | Default | Description |
|---|---|---|---|
label* | string | — | The block's name, shown in its header: "Diagram". |
render* | (source: string, ctx: BlockViewRenderContext) => React.ReactNode | — | The preview for source. Called on the client only; the server renders the source. |
actions | readonly BlockViewAction[] | — | Extra ⋯ menu items, after Copy source. |
defaultView | "preview" | "source" | "preview" | What a block opens in. A new empty block (or one still holding starter) always opens in
Code while it is being edited. |
hint | ((source: string) => string | null | undefined) | — | A one-line hint shown under the source in Code mode; null hides it. |
split | boolean | false | In the editor, Code shows source and live preview side by side when the block is at least 45rem (720px) wide. |
starter | string | — | What a new block of this kind starts with (the slash menu's and Turn into's insert). |
Accessibility
- The diagram is a
<figure>named by the source'saccTitle(else "Diagram"). The SVG keeps mermaid's ownaria-labelledby/aria-describedby, built fromaccTitleandaccDescr, and itsaria-roledescription; nothing wraps it inrole="img", so its text stays readable. - Write
accTitle:andaccDescr:in every diagram. In Code mode the block reminds the writer while either is missing. - Code is the text alternative: the source is one press away, and Copy source copies it.
- An error is a
role="status"line, announced once when it changes; it names the line. - Preview and Code are buttons with
aria-pressed. The expanded view is a dialog titled "Diagram" whose viewport pans with the arrow keys and zooms with + and −.
| Key | Action |
|---|---|
| Cmd/Ctrl + Enter | Switch between Preview and Code |
| Esc | From Code, back to Preview |
| ← → ↑ ↓ | In the expanded view, move the diagram (Shift moves further) |
| + / − | In the expanded view, zoom in or out |
| 0 | In the expanded view, fit the diagram to the screen |
| Contract | States tested |
|---|---|
| Behaviour | loading, ready, error, failed |
| Accessibility | labeled, live-region, browser-accessibility-test |
| Visual | default, loading, error |