Skip to content
Component installs need the registry setup
VegaStack Design

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
stable
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-block

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/mermaid-block

Run 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.

Every page goes through review before it is published.

Diagram
flowchart LR
  accTitle: Publishing a page
  accDescr: A draft goes to review, then is published or sent back for changes.
  draft[Draft] --> review{Review}
  review -->|Approved| published([Published])
  review -->|Changes| draft
  class draft step
  class review decision
  class published outcome

The reviewer is the space's editor.

In a page you read

Every page goes through review before it is published.

Diagram
flowchart LR
  accTitle: Publishing a page
  accDescr: A draft goes to review, then is published or sent back for changes.
  draft[Draft] --> review{Review}
  review -->|Approved| published([Published])
  review -->|Changes| draft
  class draft step
  class review decision
  class published outcome

The reviewer is the space's editor.

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

PropTypeDefaultDescription
source*string—The diagram's mermaid source — a ```mermaid fence's body.
debouncenumber0Milliseconds of quiet after source changes before re-drawing (live editing passes 400).
emptyMessagestring"Nothing to draw yet"What a diagram with nothing to draw says — an empty source, or only %% comments (a new diagram's starter).
themeMermaidTheme—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

AttributeValues
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"
PropTypeDefaultDescription
debouncenumber400Milliseconds to wait after the source last changed before drawing it again (while typing). The first drawing never waits.
enabledbooleantruefalse holds off drawing (the block is far from the viewport).
themeMermaidTheme"light"The colour scheme to draw in.
PropTypeDefaultDescription
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.
actionsreadonly 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.
splitbooleanfalseIn the editor, Code shows source and live preview side by side when the block is at least 45rem (720px) wide.
starterstring—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's accTitle (else "Diagram"). The SVG keeps mermaid's own aria-labelledby / aria-describedby, built from accTitle and accDescr, and its aria-roledescription; nothing wraps it in role="img", so its text stays readable.
  • Write accTitle: and accDescr: 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 −.
KeyAction
Cmd/Ctrl + EnterSwitch between Preview and Code
EscFrom Code, back to Preview
← → ↑ ↓In the expanded view, move the diagram (Shift moves further)
+ / −In the expanded view, zoom in or out
0In the expanded view, fit the diagram to the screen
ContractStates tested
Behaviourloading, ready, error, failed
Accessibilitylabeled, live-region, browser-accessibility-test
Visualdefault, loading, error

Do / Don't

Do
Give every diagram an accTitle and an accDescr, keep it small enough to read at its natural width, and let pages carry it as a ```mermaid fence.
Don't
Raise the security level, turn on HTML labels, or draw data that belongs in a chart.

On this page