Diagrams

Overview

Accent CMS ships a server-side diagram pipeline that turns text-based diagram sources – Mermaid flowcharts, Svgbob ASCII sketches, and plugin-declared languages (via WASM plugins) – into inline SVG. The SVG arrives in the initial page payload, so search engines and reader-mode clients see the diagram, and there is no flash of unrendered text in the browser.

Three integration levels meet authors where they already write:

  • Fenced code blocks – the shortest path. Tag a fenced block with mermaid, svgbob, or bob and the renderer takes over.
  • [diagram] shortcode – adds caption, alt text, theme override, width hints, or an external src file.
  • diagram() template function – programmatic use from theme templates and frontmatter (planned; see below).

Three render modes choose where the work happens:

ModeServer SVGClient JSDefault in
serveryes
clientyes
hybridprod buildsdevboth

hybrid is the default and is what most sites want: production builds ship fully rendered SVG, while accent serve in dev mode emits a pass-through marker so you can iterate without paying the render cost on every keystroke.

Quick start

Wrap your diagram in a fenced code block tagged mermaid:

```mermaid
flowchart LR
  A[Idea] --> B[Draft]
  B --> C{Review}
  C -->|approve| D[Publish]
  C -->|revise| B
```

renders as:

approvereviseIdeaDraftReviewPublish

Supported languages

The diagram pipeline is renderer-agnostic. Each fenced-block language is routed to the renderer that registered for it, and the supported set depends on which features were compiled in and which renderers are enabled in config.yaml.

LanguageRendererBinaryCargo feature
mermaidMermaidRenderer (via mermaid-rs-renderer)Standarddiagrams-mermaid
svgbob, bobSvgbobRendererStandarddiagrams-svgbob
plugin-declared (e.g. d2, dot)plugin renderer (Component Model)Standarddiagrams-plugins

Svgbob is a pure-Rust ASCII-art renderer with no system dependencies. All three renderers – including the plugin-declared languages – ship in the Standard floor binary and are free in accent build and non-production accent serve (free to build, pay to serve); a Standard license is only required to serve the result in production. Plugin renderers load through the WASM plugin host; see the migration section for examples of slotting in d2, dot, or Kroki-style backends.

Mermaid dialects

The Mermaid renderer covers the full 23 DiagramKind variants implemented in the upstream mermaid-rs-renderer crate. The dialect is selected by the first non-empty line of the fenced block, matched case-insensitively as a prefix – so sankey, sankey-beta, and Sankey-Beta all resolve to the same renderer:

Header keywordDialectNotes
flowchart / graphFlowchartTD/TB/LR/RL/BT directions, subgraphs, decision diamonds
sequenceDiagramSequenceactors, activations, notes, alt/opt/par/loop fragments
classDiagramClassinheritance, association, members and method visibility
stateDiagram (incl. -v2)Statestates, transitions, composite regions, notes
erDiagramErentity-relationship cardinality
piePiepie chart with optional title
mindmapMindmapradial mindmap with shape syntax
journeyJourneyuser-journey scoring
timelineTimelineevent timeline grouped by section
ganttGantttask schedule with sections, dependencies, milestones
requirementDiagramRequirementrequirements + relations (rendered via the flowchart layout)
gitGraphGitGraphgit branch / merge graph
C4Context (and other C4*)C4C4 architecture (Context / Container / Component)
sankey-betaSankeysankey flow
quadrantChartQuadrant2x2 quadrant scatter with axis labels
zenumlZenUMLsequence-diagram dialect via the zenuml syntax
block-betaBlockblock / partition diagram
packet-betaPacketpacket / byte-field layout
kanbanKanbankanban board columns
architecture-betaArchitectureinfra / service-architecture diagram
radar-betaRadarradar / spider chart
treemap-betaTreemaphierarchical treemap
xychart-betaXYChartx-y axis chart with bars and lines

The -beta suffix matches the Mermaid project’s naming for dialects still in active development upstream. Layout coverage and visual fidelity vary across the newer dialects, but the embedded font and deterministic SVG emission described in Render modes apply to every dialect equally – a diagram that renders cleanly once is byte-stable across platforms.

Dialect samples

The five primary dialects (flowchart, sequenceDiagram, stateDiagram-v2, classDiagram, erDiagram) get full copy-pasteable examples in the Agent-authoring cookbook. The remaining ten dialects each get a minimal live render below – expand a panel to see the rendered SVG. All ten render server-side through the currently pinned mermaid-rs-renderer revision; the embedded inline SVG you see when the panel opens is the same payload that ships in the production HTML, with no client-side JS and no CDN. View the page source for the original Mermaid input.

requirementDiagram -- requirements with id, text, risk, verifymethod <<Requirement>>test_reqID: 1Text: Must workRisk: MediumVerification: Test
quadrantChart -- 2x2 risk-versus-effort scatter Risk vs EffortLow EffortHigh EffortLow RiskHigh RiskAB
zenuml -- sequence-diagram dialect hellohiAliceBobAliceBob
block-beta -- block / partition diagram ABC
packet-beta -- packet / byte-field layout 0-3Source4-7Dest
kanban -- kanban board columns TodoDoing[Task[Task_1[Task_2
architecture-beta -- service / infra architecture APIDatabase
radar-beta -- radar / spider chart ABCDseries1["Series 1"]
treemap-beta -- hierarchical treemap RootB20A10C5
xychart-beta -- x-y axis chart with bars or lines Line Chart01020304050JanFebMarAprValue

Configuration

Defaults work out of the box. Override in config.yaml:

diagrams:
  enabled: true                  # master switch (default true)
  render_mode: hybrid            # server | client | hybrid (default hybrid)
  on_error: warn                 # warn | fail | silent (default warn)
  cache:
    directory: ./.diagram-cache  # default; resolved relative to config
    max_entries: 1024            # moka LRU cap
  renderers:
    mermaid:
      enabled: true              # default true
      theme: default             # default | dark | forest | neutral
      cdn_url: https://cdn.jsdelivr.net/npm/mermaid@11.15.0/dist/mermaid.esm.min.mjs
      sri_hash: ""               # empty = no SRI; pin to sha384-... when self-hosting
    svgbob:
      enabled: true              # default true
  plugins:
    enabled: true                # default; free in every edition
    timeout_ms: 5000             # default per-render budget; warning box on overrun

The cdn_url and sri_hash knobs feed the default-theme client-side loader. The loader is a small ES module that the default theme injects in dev mode (only) when the page contains at least one passthrough diagram block. To self-host Mermaid.js, set cdn_url to your origin and either compute and pin the SRI hash (recommended, once SRI emission is wired – see below) or leave sri_hash: "". Production builds (accent build or accent serve --production) server-render the SVG and never load the CDN script.

Note

The sri_hash value is validated and accepted today, but the default theme’s loader does not yet emit the matching integrity= attribute. SRI for ES module imports requires <link rel="modulepreload"> wiring that lands in a follow-up.

Render modes

  • server – always render to SVG. Best SEO and first-paint, slowest hot-reload on dev edits.
  • client – emit <pre class="diagram-passthrough"> markup for a theme-supplied JavaScript renderer (e.g. mermaid.js) to render in the browser. Useful when you want a single rendering pipeline across server and client, or when you need a Mermaid feature the upstream Rust crate doesn’t yet support.
  • hybrid (default) – server in production builds (accent build, accent serve --production), client in dev mode. Trades production SEO/first-paint for fast hot-reload during authoring.

Theme default and the polyfilled dark/forest currently render with the modern (slate-on-white) palette; neutral renders with the classic Mermaid (cream/lavender) palette. Real dark and forest palettes will land when the upstream crate ships them – your config keeps working through the upgrade.

All four themes use Accent’s embedded Inter font at the same size, so swapping theme changes colours only – element positions, edge routing, and overall layout stay identical. The embedded font also makes the rendered SVG byte-reproducible across Linux / macOS / Windows: a diagram cached on one platform serves cleanly when the build is reproduced on another.

Mixing server and client rendering on one page

The site-level render_mode sets the default for every diagram, but any individual block can opt into a different mode with a render= attribute. This lets a single page contain both server-rendered SVGs (static, indexable, fast first paint) and live Mermaid.js diagrams (interactive, latest Mermaid syntax, live click directives).

ValueBehaviour
render=serverForce server-render for this block. Output is an inline <svg>.
render=clientForce passthrough for this block. Output is <pre class="diagram-passthrough"> and the theme’s Mermaid.js loader renders it in the browser.
render=hybridIdentical to omitting the attribute – inherit the site default. Documented for completeness.
(omitted)Inherit the site render_mode.

Fenced block:

```mermaid render=client
flowchart LR
  A -->|click handler| B
  click A call openModal()
```

```mermaid render=server
sequenceDiagram
  Alice->>Bob: ping
```

[diagram] shortcode:

[diagram type="mermaid" render="client" caption="Live build flow"]
flowchart LR
  A --> B
[/diagram]

diagram() template function:

{{ diagram("mermaid", page.meta.architecture_diagram,
           render="client",
           caption="Architecture") }}

A typo (render=cient) surfaces immediately as a warning box (or fails the build under on_error: fail) so the override is never silently ignored.

The default theme’s Mermaid.js loader is included whenever a page contains at least one passthrough block, regardless of how that block was tagged. A page that opts a single block into render=client under render_mode: server still ships the loader on that page only; production output stays loader-free for pages with no passthrough blocks.

Svgbob ASCII diagrams

For lightweight box-and-arrow sketches, Accent ships svgbob out of the box. Tag a fenced block with svgbob (or the shorter bob alias) and the renderer turns ASCII art into a clean SVG:

```svgbob
+-------+    +-------+
|   A   |--->|   B   |
+-------+    +-------+
```

renders as:

B A

Svgbob is a pure-Rust renderer with no system dependencies, so it works everywhere Accent does. Disable it (or opt in to the shortcode-only path) with the same per-renderer toggle as Mermaid:

diagrams:
  renderers:
    svgbob:
      enabled: true   # default

Plugin-provided diagram languages

WASM plugins can extend the diagram pipeline with additional renderers (D2, Graphviz, PlantUML via WASM ports, etc.). This ships in the Standard floor binary and works for free in accent build and non-production accent serve. A plugin declares the languages it handles in its plugin.toml:

[plugin]
name = "d2-renderer"
version = "0.1.0"
api_version = "0.1.0"

[diagram]
languages = ["d2"]

Built-in language tags (mermaid, svgbob, bob) cannot be shadowed: a plugin that claims one of them is logged at startup and ignored.

Configure the per-render timeout and master toggle in config.yaml:

diagrams:
  plugins:
    enabled: true        # default; free in every edition
    timeout_ms: 5000     # default; plugins exceeding this surface a warning box

A plugin that exceeds timeout_ms produces the same inline warning box as a syntax error (see Error handling below). Builds without the diagrams-plugins feature parse the plugins config block but do nothing with it.

[diagram] shortcode

The fenced ```mermaid syntax is the shortest path. For diagrams that need a caption, accessible alt text, or an external source file, use the [diagram] shortcode – it routes through the same renderer and cache as fenced blocks, so output stays equivalent for the same source:

[diagram type="mermaid" caption="Build flow" alt="A flowchart showing CI build steps"]
flowchart LR
  A[Idea] --> B[Draft]
  B --> C{Review}
  C -->|approve| D[Publish]
  C -->|revise| B
[/diagram]

External-file variant – read the diagram source from a .mmd file next to the page (the body must be empty):

[diagram type="mermaid" src="diagrams/architecture.mmd" caption="System architecture"]
[/diagram]
AttributeRequiredDescription
typeyesRenderer language (mermaid, svgbob / bob, plus future plugin renderers)
srcPath to a diagram source file, relative to the current page’s directory; cannot escape via .. and absolute paths are rejected
themeTheme override (Mermaid: `default
width / heightPixel hints forwarded to the renderer
captionRendered as a <figcaption> next to the SVG
altForwarded as the SVG’s <title> for screen readers
classExtra CSS class appended to the wrapper

You must supply either a non-empty body or src – both or neither produces a warning box that respects the same on_error policy as fenced blocks.

diagram() template function

The diagram() template function is the third author path, alongside fenced blocks and the [diagram] shortcode. Use it when the diagram source comes from frontmatter, layout context, or a shared template file rather than the page body itself.

It routes through the same RenderDispatcher and DiagramCache as the other two paths, so the rendered SVG is byte-identical for the same source.

Signature

diagram(type, source, *, width=None, height=None, theme=None,
        caption=None, alt=None, class=None) -> safe html
ArgKindDescription
typepositionalRenderer language (mermaid, svgbob, bob, plugin-registered)
sourcepositionalRaw diagram source text (a string, not a path; pass it inline or from a frontmatter field)
widthkeywordSVG width in pixels
heightkeywordSVG height in pixels
themekeywordRenderer theme name
captionkeywordRendered as <figcaption> next to the SVG
altkeywordForwarded as the SVG’s <title> for screen readers
classkeywordExtra CSS class appended to the wrapper

Frontmatter-driven example

---
title: Architecture Overview
architecture_diagram: |
  flowchart LR
    Browser --> Accent
    Accent --> Markdown
---
{% if page.custom.architecture_diagram %}
  {{ diagram("mermaid", page.custom.architecture_diagram,
             caption="Architecture",
             alt="Browser hits Accent which renders Markdown") }}
{% endif %}

Output

When caption, alt, or class is set, the SVG is wrapped in <figure class="diagram-wrapper diagram-wrapper--<type>"> with a <figcaption> for the caption. Otherwise it is wrapped in a bare <div class="diagram-wrapper diagram-wrapper--<type>">. This is the same wrapping the [diagram] shortcode emits, so theme CSS rules written for one path apply to the other.

Errors

diagram() honours diagrams.on_error from the config:

  • warn (default): render an inline warning-box SVG and keep going
  • fail: raise a MiniJinja error so accent build exits non-zero
  • silent: emit an empty string

Caching

Rendered SVG is cached by SHA-256 of the normalised source plus the renderer version. Cosmetic edits (whitespace, comments, line endings) do not invalidate the cache. Editing a .md file invalidates every diagram block in that file via the file watcher; SIGHUP also clears everything.

Error handling

When a diagram source has a syntax error the renderer rejects it. Configure how Accent reports the failure with diagrams.on_error:

diagrams:
  on_error: warn   # warn (default) | fail | silent
ModeBehaviour on renderer error
warn (default)Substitute the diagram with an inline SVG warning box that shows the offending source, the structured error, and (when available) the line/col marker. Build continues.
failSame warning box, but accent build exits non-zero on the first error so CI surfaces the problem. The error message names the page URL. In accent serve the visible output matches warn.
silentSkip the warning box and emit the original fenced code block unchanged so the syntax highlighter can render it. Build continues. Useful when authors are mid-edit and the noise of a warning box is unwelcome.

The warning box is self-contained inline SVG: no external CSS, no JS, no network calls. It is themable via three CSS variables on a containing element with sensible amber/red fallbacks:

article .content {
  --diagram-warning-bg: #fef3c7;        /* background fill   */
  --diagram-warning-border: #d97706;    /* outer stroke      */
  --diagram-warning-highlight: #fde68a; /* offending-line bar */
}

For example, a malformed flowchart with an unclosed subgraph:

```mermaid
flowchart TD
  subgraph S1
    A --> B
```

renders (in warn mode) as a warning box containing the source, the parser’s expected end at line N message, and a highlight rect over the offending line. Fix the source, save, and the warning box is replaced by the rendered diagram on the next request.

Agent-authoring cookbook

LLM-generated documentation is a primary audience for the diagram pipeline: a model that emits a Mermaid flowchart inside a markdown response can have it server-rendered to SVG with no extra tooling. The patterns below all parse cleanly through mermaid-rs-renderer today. Each one is followed by its rendered output so the page itself proves the pipeline is working.

Flowchart

Linear left-to-right flowcharts (flowchart LR) and top-down trees (flowchart TD) cover most “step 1, step 2, decision, branch” prose. Square brackets are rectangles, curly braces are diamonds (decisions), and -->|label| annotates the edge.

```mermaid
flowchart LR
  A[Request] --> B{Cached?}
  B -->|hit| C[Serve cached]
  B -->|miss| D[Render]
  D --> E[Cache]
  E --> C
```
hitmissRequestCached?Serve cachedRenderCache

Sequence diagram

Sequence diagrams render conversations between named actors with arrows for messages. Use ->> for synchronous calls and -->> for responses; Note over A,B: ... annotates a span.

```mermaid
sequenceDiagram
  participant Browser
  participant Server
  participant Cache
  Browser->>Server: GET /docs/page
  Server->>Cache: lookup(page)
  Cache-->>Server: miss
  Server->>Server: render markdown
  Server-->>Browser: 200 OK (HTML)
```
GET /docs/pagelookup(page)missrender markdown200 OK (HTML)BrowserCacheServerBrowserServerCache

State machine

State diagrams describe lifecycles: [*] is the start/end pseudo-state, --> marks a transition, and a label after : annotates the trigger.

```mermaid
stateDiagram-v2
  [*] --> Draft
  Draft --> Review : submit
  Review --> Draft : reject
  Review --> Published : approve
  Published --> Archived : retire
  Archived --> [*]
```
submitrejectapproveretireArchivedDraftPublishedReview

Class diagram

Class diagrams document object models. + marks public members, - private; the <|-- arrow shows inheritance, and a plain -- is an association.

```mermaid
classDiagram
  class Page {
    +String title
    +String url
    +render() String
  }
  class MarkdownPage {
    +String source
    +parse() Events
  }
  Page <|-- MarkdownPage
```
MarkdownPage+String source+parse() : EventsPage+String title+String url+render() : String

ER diagram

Entity-relationship diagrams describe data models. Cardinality is encoded in the link symbols: ||--o{ is “exactly one to zero or many”, }o--|| is its mirror.

```mermaid
erDiagram
  AUTHOR ||--o{ PAGE : writes
  PAGE   ||--o{ TAG  : tagged-with
  AUTHOR {
    string name
    string email
  }
  PAGE {
    string title
    date   published_at
  }
```
writestagged-withAUTHORnamestringemailstringPAGEtitlestringpublished_atdateTAG

Migration guides

From Hugo

Hugo renders Mermaid via a partial template that wraps the diagram source in a <pre class="mermaid"> element and ships mermaid.js from a CDN. The shortcode invocation looks like {{< mermaid >}}flowchart LR; A --> B{{< /mermaid >}} (custom themes) or a fenced ```mermaid block in newer Hugo configurations.

In Accent the fenced block is the canonical path – no theme partial, no shortcode wiring required. The Hugo params.mermaid toggle in config.toml maps to Accent’s diagrams.enabled (which defaults to true). Mermaid theme selection moves from Hugo’s params.mermaid.theme to diagrams.renderers.mermaid.theme.

HugoAccent
{{< mermaid >}}...{{< /mermaid >}}```mermaid fenced block, or [diagram type="mermaid"]...[/diagram]
params.mermaid = truediagrams.enabled: true (default)
params.mermaid.theme = "dark"diagrams.renderers.mermaid.theme: dark
client-side render via mermaid.jsserver-side SVG by default; render_mode: client for parity

From Docusaurus

Docusaurus needs @docusaurus/theme-mermaid enabled in docusaurus.config.js and markdown.mermaid: true to recognise fenced ```mermaid blocks. The theme renders client-side: a script hydrates each block in the browser after page load.

Accent recognises the same fenced syntax with no plugin install. The key migration is the rendering policy: Docusaurus is client-only, Accent’s hybrid default is server-render in production and client-render in dev. To preserve Docusaurus-style behaviour, set render_mode: client.

DocusaurusAccent
themes: ['@docusaurus/theme-mermaid']built-in; remove
markdown.mermaid: truediagrams.enabled: true (default)
themeConfig.mermaid.theme.dark = "dark"diagrams.renderers.mermaid.theme: dark
client-only renderdiagrams.render_mode: client (or keep hybrid for prod SVG)

From MkDocs

MkDocs Material renders Mermaid via the pymdownx.superfences extension with a custom fence registered through markdown_extensions:. For non-Mermaid languages the Kroki plugin is the usual escape hatch and proxies the source to a remote rendering service.

Accent replaces both: built-in Mermaid and Svgbob support cover the common cases without any plugin install, and the diagrams-plugins feature exposes a WASM plugin hook that can host a d2, dot, or local Kroki bridge in-process – no external service.

MkDocs / MaterialAccent
pymdownx.superfences custom_fences: mermaidbuilt-in Mermaid; remove
Kroki plugin (mkdocs-kroki-plugin)diagrams-plugins with a local renderer
Material theme mermaid palettediagrams.renderers.mermaid.theme
client-side renderdiagrams.render_mode: server for full SSR

Troubleshooting

  • Diagram doesn’t render, code block shows instead – the fenced block tag (the bit immediately after the triple backticks) must match a registered renderer language exactly: mermaid, svgbob, or bob. Capitalisation matters here. Note that this is a different layer from the Mermaid dialect keyword inside the source body (flowchart, sankey-beta, etc.) – the body keyword is matched case-insensitively as a prefix, so Sankey-Beta works inside the diagram, but ```Mermaid as a fence tag does not. Also check diagrams.enabled: true and that the per-renderer enabled flag isn’t false. If on_error: silent is set, parse failures intentionally fall back to the syntax highlighter; switch to warn to see the underlying error.
  • Stale render after editing the source – the file watcher invalidates the in-memory cache when the surrounding .md file is saved, so most edits update without further action. If a stale render persists (e.g. after a deploy or a cross-process change), run accent cache clear --config <your-config.yaml> to empty the on-disk diagram cache (.diagram-cache/, under the path configured at diagrams.cache.directory). The command is idempotent and safe to run while the server is stopped or running. For a running server, follow up with pkill -HUP accent so the in-memory cache in the live process is dropped as well.
  • Warning box on a previously-good diagram – the inline SVG warning box (Mode warn or fail) names the parser error. The most common cause after upgrading is a Mermaid syntax extension added in a newer JS version that the Rust crate has not yet picked up; the workaround is render_mode: client for that page, or on_error: silent to suppress the box while you migrate.
  • Plugin renderer never fires – plugin renderers register through the WASM plugin host. Confirm the plugin’s plugin.toml declares the diagram type under its [diagram] section, and that diagrams-plugins was compiled into the binary. Built-in tags (mermaid, svgbob, bob) cannot be shadowed: a plugin that claims one is logged and ignored at startup.
  • Plugin renderer hangs or times out – the per-render budget defaults to 5000ms via diagrams.plugins.timeout_ms. A plugin that exceeds the budget produces the same inline warning box as a syntax error. Lower or raise the budget in config.yaml if your plugin’s expected render time is consistently outside the default.

Cross-references