Adaptive themed diagrams
HCI concept pages and the Next.js component architecture page share one reproducible diagram pipeline. You edit a Mermaid (.mmd) source once. site-nextjs/scripts/build-diagrams.mjs renders it and writes SVG files that follow light and dark themes. You commit those generated files next to the source.
Why this exists
Hand-drawn SVGs drift from the product palette, stay light when the reader switches to dark mode, and are hard to review in Git. The pipeline instead:
-
keeps one canonical source per diagram under
docs/modules/<module>/images/diagrams/<name>/ -
writes a themed-svg manifest (
<name>.theme.json) with light and dark token presets in theftn-diagramnamespace -
writes a standalone SVG (
<name>.svg) whose colors are CSS custom properties with built-in light values and aprefers-color-scheme: darkblock, so it adapts without JavaScript -
writes a host SVG (
<name>.host.svg), a sanitized variant that a page script can inline so the diagram follows the page’s own theme toggle -
copies the standalone and host SVGs of published diagrams into
site-nextjs/public/images/for the Next.js demo pages
There is one SVG pair per diagram, not separate light and dark files: the same file carries both palettes.
Related HCI essays: Context Rails and Role lens UI.
Files per diagram
docs/modules/<module>/images/diagrams/<name>/
├── <name>.mmd # canonical Mermaid source — edit this
├── <name>.theme.json # generated manifest (do not hand-edit)
├── <name>.svg # generated standalone adaptive SVG
└── <name>.host.svg # generated host SVG for in-page upgrade
When a registry entry has publish: true, the generator also writes:
site-nextjs/public/images/<name>.svg
site-nextjs/public/images/<name>.host.svg
The four HCI diagrams that were migrated from hand-drawn SVGs also keep the original as <name>.legacy.fixed.svg in the same folder. The generator does not read or write that file.
Antora pages reference the generated standalone SVG with the themed-svg role:
image::diagrams/context-rails-overview/context-rails-overview.svg[alt="…",role=themed-svg,width=720]
Registered diagrams
The registry is the diagrams array in site-nextjs/scripts/build-diagrams.mjs.
| Diagram | Antora module | Copied to site-nextjs/public/images/ |
|---|---|---|
|
|
yes ( |
|
|
yes ( |
|
|
no (docs only) |
Diagrams outside the pipeline
-
hci-role-lens-mockupis a UI mockup, not a Mermaid diagram. Its folder has no.mmdand it is not in the registry. Its.svg,.host.svgand.theme.jsonwere derived once fromhci-role-lens-mockup.legacy.fixed.svgand are maintained by hand, together with the copies insite-nextjs/public/images/.pnpm diagrams:checkdoes not cover it. -
The Git/Vercel topology drawing is an Excalidraw source with fixed exports in
docs/modules/nextjs/images/:Git Structure - Vercel Free.excalidraw,Git Structure - Vercel Free.svgandGit Structure - Vercel Free - dark.svg. Move it to Mermaid if you want it themed.
Commands
Install once from the repository root, then run the diagram scripts from site-nextjs/:
pnpm install # repository root
cd site-nextjs
pnpm diagrams:generate # render every registered diagram and write all outputs
pnpm diagrams:check # render in memory; exit 1 and print "stale: <path>" for any output that differs
diagrams:generate runs node ./scripts/build-diagrams.mjs, and diagrams:check runs the same script with --check. The script also accepts --migrate, a one-time helper that moved the original HCI sources and hand-drawn SVGs into the per-diagram folders. You should not need it again.
Regenerate whenever you change a .mmd file or the token, binding or registry tables in build-diagrams.mjs. Then commit the source and every generated file.
Where the check runs
-
pnpm buildinsite-nextjs/runspnpm brand:check && pnpm diagrams:check && next build. Use it locally before you push diagram changes. -
Vercel builds with
pnpm build:vercel(site-nextjs/vercel.json), which runssite-nextjs/vercel-build.mjs. That callspnpm build:app(next build, behindconvex deployin production) and does not rundiagrams:check. Stale diagrams do not fail a deploy, so run the check yourself. -
The GitHub workflow
.github/workflows/deploy-vercel.ymldoes not run it either.
How rendering works
For each registry entry the script:
-
reads
docs/modules/<module>/images/diagrams/<name>/<name>.mmdand stops withMissing canonical sourceif it is absent -
renders it with
@mermaid-js/mermaid-cli(site-nextjs/node_modules/@mermaid-js/mermaid-cli/src/cli.js) to a raw SVG on a transparent background -
passes the raw SVG and the manifest to
prepareThemedMermaidSvgDualOutputfrom@dev-centr/mermaid-svg-css-vars, which returns the standalone and host SVGs -
writes the manifest, standalone and host files, plus the
public/imagescopies whenpublishis true
The Mermaid and Puppeteer settings are written to a temporary folder on every run. There is no committed config file to edit:
-
Mermaid config:
htmlLabels: false(top level andflowchart),flowchart.curve: basis,securityLevel: strict,theme: default. -
Puppeteer config:
{"args": ["--no-sandbox", "--disable-setuid-sandbox"]}.
Browser for Puppeteer
Mermaid CLI drives a headless Chromium through Puppeteer. The script picks the browser in this order:
-
PUPPETEER_EXECUTABLE_PATH, if set. -
On Windows, the first of
C:\Program Files\Google\Chrome\Application\chrome.exeandC:\Program Files (x86)\Microsoft\Edge\Application\msedge.exethat exists. -
Otherwise, the browser that Puppeteer’s postinstall downloaded into its cache (
~/.cache/puppeteer).
The download only happens when pnpm lets Puppeteer run its install script. Root pnpm-workspace.yaml allows that with puppeteer: true under allowBuilds.
Colors and class names
Every diagram uses the same manifest, defined in build-diagrams.mjs:
-
tokens: ten color tokens (color.surface.primary…color.surface.danger,color.text.primary,color.edge,color.edge.label,color.border.primary), each with a light and a dark value. They become custom properties such as--themed-svg-ftn-diagram-color-surface-primary. -
Bindings map Mermaid output to those tokens. Background fills are bound through six class names:
primary,secondary,accent,success,warninganddanger. Node text, subgraph titles, borders, edges, edge labels and arrowheads are bound for every diagram.
To color a node, assign one of the six class names in the .mmd file (class myNode accent). Subgraphs work the same way once they have an id (subgraph stores [Stores], then class stores primary). Without a class, a subgraph keeps Mermaid’s light default background in dark mode. Keep a matching classDef line for each class so the diagram still looks right in plain Mermaid previews; the pipeline replaces those colors with the theme tokens. A class name outside the six keeps Mermaid’s hard-coded colors and will not change in dark mode.
Per-diagram Mermaid settings can go in a front-matter config: block at the top of the .mmd file. react-component-architecture.mmd uses one to raise flowchart.wrappingWidth so long file paths stay on one line.
Where diagrams are upgraded
The standalone SVG already adapts to the reader’s operating-system color scheme. Pages that have their own theme toggle swap it for the host SVG so the diagram follows the toggle instead:
-
Docs site (
docs.foodtrucknerdz.com): theFoodTruckNerdz/docssupplemental UI scriptsite/supplemental-ui/js/site-themed-svg.jsmarksimg.themed-svgand.themed-svg img(whatrole=themed-svgproduces) and loads the matching.host.svgbeside each image. -
Next.js app:
site-nextjs/components/theme/themed-svg-boot.tsx(mounted inapp/layout.tsx) callsdefineThemedSvgElement()andupgradeThemedSvgImages(document)from@dev-centr/themed-svg/runtimeon every route change. It upgradesimg[data-themed-svg]elements. The demo pages/demo/role-lensand/demo/context-rails-unificationuse that attribute with thepublic/images/<name>.svgfiles. -
In the app,
site-nextjs/app/globals.csssets the--themed-svg-ftn-diagram-*values for the light (:root) and dark (.dark) site themes. Those values can differ from the manifest presets (for example accent fills tuned to the product palette), so an upgraded diagram in the app can look slightly different from the same standalone SVG.
Adding or changing a diagram
-
Create
docs/modules/<module>/images/diagrams/<name>/<name>.mmd. -
Add an entry to the
diagramsarray insite-nextjs/scripts/build-diagrams.mjswithname,module,titleanddescription. Addpublish: trueonly if the Next.js app needs a copy inpublic/images/. Thetitleanddescriptionbecome the SVG’s accessible title and description. -
Color nodes and subgraphs with the six class names described above.
-
Run
pnpm diagrams:generatefromsite-nextjs/, thenpnpm diagrams:checkto confirm the outputs are current. -
Reference the generated SVG from the Antora page in that module:
image::diagrams/<name>/<name>.svg[alt="…",role=themed-svg,width=720]. -
Commit the
.mmdsource and all generated files.
Packages
The pinned versions are in site-nextjs/package.json (devDependencies):
-
@mermaid-js/mermaid-cli: headless Mermaid rendering through Puppeteer -
@dev-centr/mermaid-svg-css-vars: turns Mermaid SVG plus a manifest into the standalone and host outputs -
@dev-centr/themed-svg: browser runtime that upgrades marked images to host SVGs
Manifest schema: schema/themed-svg-manifest-v1.schema.json in the @dev-centr/themed-svg package (source on GitHub).