jixoai-ui
the terminal design language
loading the style sheet…
Skip to content
docs · print medium: screen

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.

install — mermaid

$npx jixoai-ui add mermaid

https://ui.jixoai.com/r/mermaid.json

based on Mermaid floor → lazy svg swap token-derived palette · auto follow zoom + pan · copy source

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.

Mermaid usage
<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.

deploy-flow.mmd
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]
mermaid-usage.svelte svelte
<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 floor

stateDiagram-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
mermaid-kinds-demo.svelte svelte
<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 --> [*]
mermaid-theme-demo.svelte svelte
<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 --> E

theme="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
mermaid-backdrop-demo.svelte svelte
<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
mermaid-zoom-demo.svelte svelte
<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)
mermaid-error-demo.svelte svelte
<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

KeyAction
TabReaches the copy control, then zoom out / reset / in (real buttons, press physics)
Enter / SpaceActivates the focused control
scrollPans the scaled diagram inside the viewport (both axes)

ARIA

AttributeValueDescription
role="img"on the viewportThe diagram region, named at all times — an empty-string name falls through the ladder rather than mounting a nameless img.
aria-labelname | labels.diagram | DiagramThe trimmed ladder: consumer name first, the localized diagram label second, the shipped English last.
aria-labelcopy / zoom in / zoom out / resetOn the controls; the copy label flips for the 1.6s feedback window.
role="status"on the error stripThe 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.

TokenDefaultSource
--readonly-code-bg / -bordermuted/background mixescolor
--readonly-code-meta-bg / -fgaccent mixescolor
--jx-mermaid-floor-min6remstructural
--scrollbar-thumb*currentColor thincolor
themeVariablesderived from live tokenscolor

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

axes.mmd
flowchart LR
  axes[size] --> engine{render}
steps.mmd
flowchart LR
  named[steps resolve via the alias ladder]
mermaid-universal.svelte svelte
<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

PropertyTypeDefaultDescription
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 booleantrueThe 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 booleantrueCopy control on the footer bar (payload = the raw source, press physics, clipboard fallback).
zoomable booleantrueZoom 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

PropertyTypeDefaultDescription
size'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopethe base scale — root font-size; parts size in em
shape'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto' · ambient scopecorner geometry (CSS corner-shape; §14 degrade table)
radius'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopecorner size; auto = the concentric broadcast (§3)
density'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopespacing/leading scale over the kernel channels (§4)
color'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto' · ambient scopethe hue axis of the fixed oklch primary system (§5)
theme'light' | 'dark' | 'system' | 'auto''auto' · ambient scopelight/dark profile; system = the JS-mutable global (§6)
elevation'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto' · ambient scopeofficial M3 levels over the surface ladder (§7)
motion'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto' · ambient scopeintensity across the motion kernels (§8)
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h