Mermaid
Mermaid renders diagrams (flowcharts, sequence, pie,
git, gantt…) from text to SVG. @ponchia/ui does not render diagrams — it
themes the ones you already have and lets you annotate them, the same way
it dresses any other SVG figure in a report. Two things ship:
@ponchia/ui/mermaid— a resolved Mermaidbasetheme, per bronto theme.@ponchia/ui/mermaid.json— the same data as plain JSON, for any consumer.
Mermaid stays the consumer's renderer. This is config only: bronto never imports or runs Mermaid, and Mermaid is not a dependency.
Theme a diagram
Mermaid's base theme is the only one meant to be customized. Hand it the
bronto theme variables:
import mermaid from 'mermaid';
import { brontoMermaidTheme } from '@ponchia/ui/mermaid';
mermaid.initialize(brontoMermaidTheme()); // light
await mermaid.run();
brontoMermaidTheme(theme) returns { theme: 'base', themeVariables } ready to
spread into initialize. Pass 'dark' for the dark palette, or read the active
bronto theme:
const theme = document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light';
mermaid.initialize(brontoMermaidTheme(theme));
You can also splice the variables into a single diagram via an init directive,
or read the raw maps (import { mermaid } from '@ponchia/ui/mermaid' →
{ light, dark }). For a build step or non-JS host, read
@ponchia/ui/mermaid.json directly.
Over http(s), import the helper from a CDN as an ES module — no bundler.
tokens/mermaid.js (the @ponchia/ui/mermaid entry) has zero dependencies,
so it loads directly as a browser ES module; you get brontoMermaidTheme(theme)
itself, not a frozen object to keep in sync. Pin the package version; this needs
a real origin (not file:// — inline or pre-render to SVG there, per the note
below):
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
import { brontoMermaidTheme } from 'https://cdn.jsdelivr.net/npm/@ponchia/ui@VERSION/tokens/mermaid.js';
mermaid.initialize(brontoMermaidTheme('light'));
await mermaid.run();
</script>
There is no classic-script path. Mermaid v11 ships no UMD bundle — its
dist/is ESM-chunked, so vendoring amermaid.min.jsfor a plain<script src>is a dead end. In the browser it is the ES-module import above (over a realhttp(s)origin); for everything else, pre-render to a frozen SVG withmmdc(below) so there is no runtime at all.
file:// portability. A report opened straight from disk (
file://) cannotimportthe@ponchia/ui/mermaidmodule norfetch('…/mermaid.json')— the browser blocks both across thenull/file origin (CORS), exactly as with Vega. So for a double-clickable or PDF-bound report either inline the theme variables (generate the paste-ready literal withnpm run emit:theme mermaid light/dark— guard it from token drift withnpm run emit:theme:check <file>) or, better, pre-render to a frozen SVG with the Mermaid CLI (mmdc, below) so there is no runtime at all. Over anhttp(s)origin theimport/fetchforms both work.
The result is monochrome surfaces and lines with the rationed accent reserved
for notes — a diagram that looks like the rest of a bronto surface. The accent is
resolved into the theme map you pass to initialize, not read live from
--accent: to change it you re-initialize with the new palette and re-render;
an already-drawn diagram does not live-reskin (see below).
Why resolved colours, not var(--x)
Mermaid's theming engine derives shades (lighten/darken) from your seed colours,
so it only understands real colours — a var(--accent) string breaks
derivation. The theme map therefore ships resolved hex/rgba per theme,
projected from the same token source that feeds
tokens/resolved.json and charts.json. That projection
is the bridge a CSS-first system needs until Mermaid can read CSS variables
directly. It also means the theme is static per theme: on a light/dark
switch, re-initialize with the other palette and re-render.
Which diagrams get the palette
- Chart-like diagrams carry a categorical series palette — pie
(
pie1…pie12), git (git0…git7), and user-journey (fillType0…fillType7) are wired to the CVD-safe charts palette (blue first; no slot is the accent). - Structural diagrams — flowchart, sequence, class, ER, state — use the
monochrome node/edge/cluster grammar and spend the accent only on notes.
Edges (
lineColor) are--edge, which holds 3:1 against the canvas; node borders stay on the--line-stronghairline. - Not themed:
ganttandtimeline. Their colours come from diagram-specific slots (taskBkgColor,sectionBkgColor*,gridColor,todayLineColor, …) that the basethemeVariablesmap does not set, so a gantt/timeline renders with Mermaid's own off-brand defaults (red/grey bars), not the bronto palette. For a report timeline, prefer the native, fully-themedui-timelineprimitive (it also prints and needs no renderer); reach for a Mermaid gantt only when you specifically need its scheduling layout, and accept its default colours.
Pie wedge labels (pieSectionTextColor) sit on arbitrary palette colours; if a
label is hard to read on a given wedge, prefer a legend or direct labels over
recolouring.
Fit to small screens
The theme map sets colours, not size — sizing stays Mermaid's. Keep useMaxWidth
on (the default for most diagram types) so each SVG scales to its container
instead of forcing page-wide horizontal scroll:
mermaid.initialize({
...brontoMermaidTheme(theme),
flowchart: { useMaxWidth: true },
sequence: { useMaxWidth: true },
});
A genuinely wide diagram (a long flowchart, a big ER) can still be unreadable once squeezed onto a phone. Rather than let it overflow the page, wrap the render in a scroll container so the diagram scrolls, not the document — the same pattern the report layer uses for wide figures and tables:
.diagram-scroll {
overflow-x: auto;
}
Annotate a diagram
Mermaid output is SVG, and the annotation layer is
renderer-agnostic SVG — so a <g class="ui-annotation"> callout composes onto a
rendered diagram the same as onto any figure. bronto supplies the geometry and
the styling; placing a callout on a specific node is yours, because that
means reading Mermaid's laid-out coordinates, which bronto deliberately does not
own (no scales, no DOM, no hit-testing — see annotations.md).
The robust path is build-time, mirroring the static-report recipe:
- Pre-render the diagram to a frozen SVG with the Mermaid CLI
(
mmdc -t base -i diagram.mmd -o diagram.svg, feeding the samethemeVariables). - From that SVG, read the target node's box (
getBBox()/ the node'stransform), accounting for the diagram's root<g>translate and theviewBox. - Compute the callout strings with
@ponchia/ui/annotationsand paste a<g class="ui-annotation …">into the SVG at those coordinates.
The output is a static, on-brand, annotated SVG with no runtime — ideal for the report layer.
To annotate a live client render instead, run the same measure-and-inject
pass on the SVG returned by mermaid.render(), after it is in the DOM. Two
honest caveats, both inherent to overlaying a foreign renderer (not bronto
limitations):
- Mermaid's internal SVG structure is not a public contract. Node ids and classes are undocumented and have changed across major versions. Match nodes by your own author-supplied id substring, measure positions rather than hard-coding them, and pin your Mermaid version.
- Every re-render replaces the SVG, discarding injected overlays. Re-apply
the annotation pass after each
render()/ theme switch / resize.
Scope
bronto owns the theme map (gated: every value resolves to a colour, both themes,
no var() leaks) and the annotation geometry. It does not own Mermaid's
rendering, its internal DOM, or its re-render lifecycle — those stay Mermaid's,
and the overlay above is a documented composition, not a shipped runtime binding.