registry:ui · Data Display
mermaid — the diagram surface, source floor first
The diagram surface under the code-card progressive-enhancement law: prerender paints the escaped diagram source as a readable zero-JS floor, hydration lazy-loads the engine (the mermaid-engine singleton, ~1MB code-split) and swaps in the sanitized SVG with a reduced-motion-respecting fade over a min-height reserve (--jx-mermaid-floor-min, consumer-tunable). Theme follows the flip AUTOMATICALLY: the surface passes its own figure as the engine's themeRoot (scoped containers resolve THEIR tokens) and an effective-scope observer re-renders on any ancestor theme change — explicit 'light'/'dark' pins read the target sheet through a local wrapper. When the effective theme resolves DARK and backdrop is on (the default), the viewport paints the designed zero-ink veil — a subtractive backdrop-filter chain (blur/contrast/brightness/saturate) over the page behind, computed background transparent, carrying the card dialect's radius, inner padding and a 1px token-law border — while the derived dark palette lifts its node fills toward white through the palette's own tokens for graphics contrast on the veil ground; backdrop={false} opts out (transparent as light mode) and where backdrop-filter is unsupported the component's own opaque theme ground returns (the standing surface-ground floor). Light themes never paint a veil. Zoom (±0.25 steps, clamp 0.5–3, reset) is a pure transform with the viewport as the two-axis pan surface — the recorded scroll-run exemption (thin token scrollbars both axes, NO shared chrome inside), plus a copy-source control with the labels localization payload. A parse failure paints the error summary strip and KEEPS the source floor standing.
$npx jixoai-ui add mermaid
https://ui.jixoai.com/r/mermaid.json
usage
Usage
Source is always a runtime prop — the code-card rule. The floor (the escaped source, readable, zero JS) paints at prerender; hydration lazy-loads the ~1MB engine and swaps in the rendered, sanitized SVG.
<script lang="ts">
import Mermaid from '@ui/mermaid';
</script>
<!-- the source prop is a runtime string (the code-card rule);
the prerendered floor shows it verbatim until hydration swaps
the rendered svg in -->
<Mermaid
name="deploy-flow.mmd"
source={flowchartSource}
/>
<!-- an explicit pin reads the target sheet even under the opposite
live theme — no observer, no global class mutation -->
<Mermaid theme="dark" source={stateSource} zoomable={false} />mermaid
The complete card: filename tab (the name prop), the two-axis pan viewport, the copy control and the zoom trio. The playground swaps the theme mode — auto follows the LIVE scope (the site toggle, or this card's stage when the canvas pins one — scoped containers resolve their own tokens), explicit pins read the target sheet through a local probe wrapper.
flowchart TD
A[write the diagram source] --> B{which engine?}
B -- math --> C[lib/katex]
B -- diagram --> D[lib/mermaid-engine]
C --> E[(registry payload)]
D --> E
E --> F[npx jixoai-ui add]
F --> G[consumer site]<script lang="ts">
import Mermaid from '@ui/mermaid';
</script>
<!-- source is a runtime prop; theme follows the site by default -->
<Mermaid name="deploy-flow.mmd" theme="auto" source={flowchartSource} />diagram kinds
Sequence, state, pie — every grammar the engine speaks
The surface is grammar-agnostic: whatever mermaid parses renders. Each card here is a separate instance — distinct render ids, one shared lazy engine, renders serialized through the engine's promise chain.
mermaid · kinds
sequenceDiagram — the surface's own lifecycle
sequenceDiagram
participant P as page
participant S as surface
participant E as engine
P->>S: mount — the source floor paints (zero JS)
S->>E: renderDiagram(source, id)
E->>E: readThemeTokens(themeRoot)
E->>E: initialize + render (serial queue)
E-->>S: sanitized svg
S-->>P: fade-in swap over the floorstateDiagram-v2 — the data-state machine
stateDiagram-v2
[*] --> floor
floor --> rendering : hydration lazy-loads
rendering --> rendered : parse ok
rendering --> error : parse fail
error --> rendering : source change
rendered --> rendering : theme flip re-derives
rendered --> [*]pie showData — the cScale rides the chart tokens
pie showData
title Registry payload share by lane
"math engine" : 38
"diagram engine" : 52
"tokens + utils" : 10<script lang="ts">
import Mermaid from '@ui/mermaid';
const sequenceSource = `sequenceDiagram
participant P as page
participant S as surface
participant E as engine
P->>S: mount — the source floor paints (zero JS)
S->>E: renderDiagram(source, id)
E->>E: readThemeTokens(themeRoot)
E->>E: initialize + render (serial queue)
E-->>S: sanitized svg
S-->>P: fade-in swap over the floor`;
const stateSource = `stateDiagram-v2
[*] --> floor
floor --> rendering : hydration lazy-loads
rendering --> rendered : parse ok
rendering --> error : parse fail
error --> rendering : source change
rendered --> rendering : theme flip re-derives
rendered --> [*]`;
const pieSource = `pie showData
title Registry payload share by lane
"math engine" : 38
"diagram engine" : 52
"tokens + utils" : 10`;
</script>
<div class="flex flex-col gap-5">
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
sequenceDiagram — the surface's own lifecycle
</p>
<Mermaid source={sequenceSource} class="w-full max-w-[40rem]" />
</div>
<div class="grid gap-5 min-[760px]:grid-cols-2">
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
stateDiagram-v2 — the data-state machine
</p>
<Mermaid source={stateSource} class="w-full" />
</div>
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
pie showData — the cScale rides the chart tokens
</p>
<Mermaid source={pieSource} class="w-full" />
</div>
</div>
</div>theming
Theme follow is automatic — pins read the target sheet
Every themed fill below is derived from the live design tokens: probes inside each figure resolve the var() chains, color-utils converts to mermaid-safe hex, and the one-source-per-field table maps them onto theme 'base'. Auto follows any ancestor flip; an explicit pin stays put.
mermaid · theme pins
theme="light" (pinned)
stateDiagram-v2
[*] --> floor
floor --> rendering : hydration lazy-loads
rendering --> rendered : parse ok
rendering --> error : parse fail
error --> rendering : source change
rendered --> rendering : theme flip re-derives
rendered --> [*]theme="dark" (pinned)
stateDiagram-v2
[*] --> floor
floor --> rendering : hydration lazy-loads
rendering --> rendered : parse ok
rendering --> error : parse fail
error --> rendering : source change
rendered --> rendering : theme flip re-derives
rendered --> [*]<script lang="ts">
import Mermaid from '@ui/mermaid';
const stateSource = `stateDiagram-v2
[*] --> floor
floor --> rendering : hydration lazy-loads
rendering --> rendered : parse ok
rendering --> error : parse fail
error --> rendering : source change
rendered --> rendering : theme flip re-derives
rendered --> [*]`;
</script>
<div class="grid gap-5 min-[760px]:grid-cols-2">
<div class="flex flex-col gap-2">
<p class="font-nav text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
theme="light" (pinned)
</p>
<Mermaid theme="light" source={stateSource} zoomable={false} copyable={false} class="w-full" />
</div>
<div class="flex flex-col gap-2">
<p class="font-nav text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
theme="dark" (pinned)
</p>
<Mermaid theme="dark" source={stateSource} zoomable={false} copyable={false} class="w-full" />
</div>
</div>Flip the site's theme toggle and every auto card on
this page re-renders — the surface passes its own figure as the engine's themeRoot, so scoped containers (a .jx-light canvas stage, a dark panel) resolve
THEIR tokens, never the page's. The pinned pair above does not move: explicit
modes read the target sheet through a temporary local wrapper — the values come
out right even under the opposite live root, and the global root is never mutated.
backdrop
The dark backdrop is subtractive ink — never a tint
When the EFFECTIVE theme is dark, the viewport paints a designed veil built on backdrop-filter: a blur + contrast/brightness chain SUBTRACTS the page behind toward the dark ground. The veil layer itself paints zero background (the subtraction ink law), in a rounded, padded, 1px-bordered box — replacing the opaque hard-edge fill this surface used to paint.
mermaid · backdrop
theme="dark" + backdrop (default on)
flowchart TD
A[source floor] --> B[hydrated svg]
B -- theme flip --> C[re-derived palette]
B --> D[zoom + pan]
C --> E([registry payload])
D --> Etheme="dark" + backdrop={false}
flowchart TD
A[source floor] --> B[hydrated svg]
B -- theme flip --> C[re-derived palette]
B --> D[zoom + pan]
C --> E([registry payload])
D --> E<script lang="ts">
import Mermaid from '@ui/mermaid';
</script>
<div class="grid gap-5 min-[760px]:grid-cols-2">
<div class="flex flex-col gap-2">
<p class="font-nav text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
theme="dark" + backdrop (default on)
</p>
<Mermaid theme="dark" source={backdropSource} zoomable={false} copyable={false} class="w-full" />
</div>
<div class="flex flex-col gap-2">
<p class="font-nav text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
theme="dark" + backdrop={'{false}'}
</p>
<Mermaid theme="dark" backdrop={false} source={backdropSource} zoomable={false} copyable={false} class="w-full" />
</div>
</div>The veil rides the EFFECTIVE theme — the same token
resolution the palette uses: a dark pin on this light stage veils (the left card), theme="auto" inside a dark scope veils, and light
themes never paint a backdrop. backdrop={false} (the right card) opts out to full transparency. Where backdrop-filter is unsupported, the component's own
opaque theme-ground fill returns — the diagram never loses its ground. The dark
palette's node fills are lifted toward white through their own tokens so fills,
borders, labels, and connectors clear the WCAG thresholds on the subtractive ground
(labels ≥ 4.5:1, graphics ≥ 3:1 — probe-sampled receipts in this change's
research/w2/).
controls
Zoom & pan — a pure transform
The zoom trio steps ±0.25 (clamped 0.5–3) and resets to 1; the scale rides a transform on the inner wrapper while the viewport becomes the pan surface. No re-render, no engine call — zooming never queues a render.
mermaid · zoom & pan
zoom the sequence above 100% and pan
sequenceDiagram
participant P as page
participant S as surface
participant E as engine
P->>S: mount — the source floor paints (zero JS)
S->>E: renderDiagram(source, id)
E->>E: readThemeTokens(themeRoot)
E->>E: initialize + render (serial queue)
E-->>S: sanitized svg
S-->>P: fade-in swap over the floor<script lang="ts">
import Mermaid from '@ui/mermaid';
const sequenceSource = `sequenceDiagram
participant P as page
participant S as surface
participant E as engine
P->>S: mount — the source floor paints (zero JS)
S->>E: renderDiagram(source, id)
E->>E: readThemeTokens(themeRoot)
E->>E: initialize + render (serial queue)
E-->>S: sanitized svg
S-->>P: fade-in swap over the floor`;
</script>
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
zoom the sequence above 100% and pan
</p>
<Mermaid source={sequenceSource} class="w-full max-w-[36rem]" />
</div>The viewport is the recorded two-axis exemption from the shared scroll-run system: a pan surface for scaled content is not a linear overflow strip, so it rides the theme's thin currentColor scrollbars on BOTH axes and mounts no nudge chips or edge veils — math-block's horizontal strip is the surface that rides the full shared contract instead.
errors
A parse failure keeps the floor standing
data-state=error paints the error summary strip (the diagnostic's first line) ABOVE the source floor — the floor never disappears on failure, the same fallback law as code-card.
mermaid · error floor
a source the parser rejects
this is not a diagram
(the parser rejects it)<script lang="ts">
import Mermaid from '@ui/mermaid';
const invalidSource = `this is not a diagram
(the parser rejects it)`;
</script>
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
a source the parser rejects
</p>
<Mermaid source={invalidSource} class="w-full max-w-[36rem]" />
</div>Fix the source and the next render succeeds — the engine's serial queue is
rejection-recovering, so a failed render never poisons the next one. The summary
strip's vocabulary is localizable through labels.renderError.
a11y
Accessibility
The viewport is a role=img with a NON-EMPTY accessible name at all times — the trimmed ladder name → labels.diagram → 'Diagram'; the floor is a readable pre; every control is a real button.
Keyboard
| Key | Action |
|---|---|
| Tab | Reaches the copy control, then zoom out / reset / in (real buttons, press physics) |
| Enter / Space | Activates the focused control |
| scroll | Pans the scaled diagram inside the viewport (both axes) |
ARIA
| Attribute | Value | Description |
|---|---|---|
| role="img" | on the viewport | The diagram region, named at all times — an empty-string name falls through the ladder rather than mounting a nameless img. |
| aria-label | name | labels.diagram | Diagram | The trimmed ladder: consumer name first, the localized diagram label second, the shipped English last. |
| aria-label | copy / zoom in / zoom out / reset | On the controls; the copy label flips for the 1.6s feedback window. |
| role="status" | on the error strip | The render-error summary announces itself; the source floor below keeps standing. |
theming
Tokens
The shell borrows the readonly-code formulas (self-sufficient with only the jixoai-theme sheet installed); the diagram palette itself derives from the live tokens through the probe pipeline.
| Token | Default | Source |
|---|---|---|
| --readonly-code-bg / -border | muted/background mixes | color |
| --readonly-code-meta-bg / -fg | accent mixes | color |
| --jx-mermaid-floor-min | 6rem | structural |
| --scrollbar-thumb* | currentColor thin | color |
| themeVariables | derived from live tokens | color |
axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. SEVEN lanes — the engine-token theme literal owns the theme name (the code-card precedent, §13 rules no rename); the axis surface rides the figure root, the diagram engine itself stays outside the supply set.
Mermaid · universal props
flowchart LR
axes[size] --> engine{render}flowchart LR
named[steps resolve via the alias ladder]<Mermaid size={18} density="small">…</Mermaid>api
API
Ten props plus the HTML rest; source is the only required one — everything else is composition.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| source * | string | — | Diagram source (runtime prop — the code-card rule: never markup-inlined text). |
| name | string | — | Head tab label + the render-id base (sanitized to [a-z0-9-]; illegal input falls to the jx-mermaid default). |
| backdrop | boolean | true | The dark veil switch: when the EFFECTIVE theme is dark, the viewport paints a subtractive backdrop-filter veil (blur + contrast/brightness chain, zero background ink — the subtraction ink law) in a rounded, padded, 1px-bordered box; false = no veil and no ground (transparent, exactly as a light surface). Degrades to the component’s own opaque theme-ground fill where backdrop-filter is unsupported. |
| copyable | boolean | true | Copy control on the footer bar (payload = the raw source, press physics, clipboard fallback). |
| zoomable | boolean | true | Zoom trio — ±0.25 steps clamped 0.5–3, reset; a pure transform, no engine call. |
| labels | MermaidLabels | — | Localization payload: { copy, copied, zoomIn, zoomOut, zoomReset, renderError, diagram }; absent = English verbatim. |
| config | MermaidConfig | — | mermaid's own config — the engine's precedence ladder applies (startOnLoad/securityLevel/theme are protected; user themeVariables overlay field-wise over the derived palette). |
| class | string | '' | Merged onto the figure through cn(). |
| …rest | HTMLAttributes<HTMLElement> | — | Spread onto the figure BEFORE the component’s own data-kind/data-jx-mermaid stamps — consumer attributes land, component semantics stay un-overridable. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |