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 author paths 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.
  • The {% diagram %} tag – for a diagram in a page that needs a caption, alt text, a theme override, or width hints, with its source in the tag’s body or in a file beside the page.
  • diagram() template function – for a diagram whose source comes from frontmatter, a layout context, or a literal string in a template.

Three render modes choose where the work happens:

ModeServer SVGClient JSDefault in
serveryes––
client–yes–
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

With a caption and alt text

Write the {% diagram %} tag when a diagram needs a caption, alt text, or a class. It renders through the same pipeline and cache as a fenced block:

{% diagram type="mermaid" caption="The publishing flow" alt="An idea becomes a draft, and review approves it for publishing or sends it back" %}
flowchart LR
  A[Idea] --> B[Draft]
  B --> C{Review}
  C -->|approve| D[Publish]
  C -->|revise| B
{% /diagram %}

renders as:

approvereviseIdeaDraftReviewPublish
The publishing flow

To keep a long diagram out of the page, put its source in a file beside the page and name it: {% diagram type="mermaid" src="flow.mmd" /%}. See Builtin components.

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.17.2/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

With enabled: false, every diagram renders as its source in a code block: a fenced block, a {% diagram %} tag, and a diagram() call in a template. No page fetches a diagram library.

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.

Ad-hoc serving (accent serve <file.md> or accent serve <dir>) always renders diagrams server-side: the built-in viewer theme has no client-side loader, so Mermaid and Svgbob blocks arrive as inline SVG exactly as a production build would emit them.

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() 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 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.2.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.

Captions, alt text, and other options

A fenced block takes one attribute, render. Everything else – a caption, accessible alt text, a theme override, width hints, an extra CSS class – is set on the diagram() template function described below.

There used to be a [diagram] shortcode carrying those attributes in content. It was removed with the rest of the square-bracket syntax; see Shortcodes have been removed. The shortcode also read its source from an external .mmd file next to the page, and that has no direct replacement: put the source in the fenced block, or in frontmatter where diagram() can reach it.

diagram() template function

The diagram() template function is the second author path, alongside fenced blocks. 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 a fenced block, 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
altkeywordWritten as the wrapping figure’s aria-label for screen readers, on the server, client and plugin paths alike; empty alt text counts as none
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>">. A fenced block’s SVG is not wrapped, except in development, where it sits in a <div> with the same classes for the dev overlay.

Accent never edits the SVG itself. If a renderer gives its SVG a name of its own, such as a <title>, a screen reader can announce that name too, for the image inside the labelled figure.

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, the renderer and its version, the options that reach the rendered bytes (theme, width, height, and for a plugin renderer the page it is on), and the schema fingerprint. A caption and alt text are written around the picture, so editing either never re-renders the diagram. Edits at the edges of the source – trailing whitespace, line endings, blank lines before or after it – do not invalidate the cache; any other edit does, a comment included. Editing a .md file invalidates every diagram block in that file via the file watcher; a configuration reload (SIGHUP or POST /_admin/reload) also clears everything.

Diagrams persist between runs

The cache has two tiers. The in-memory tier serves repeat renders inside one process; the on-disk tier, under diagrams.cache.directory, outlives it. A second build reuses what the first rendered, and a restarted server does not start cold. accent build creates the directory itself, so a CI job that has never run accent serve still gets the benefit.

On a site with 1000 diagrams, a second build spends about 80% less CPU than the first. The wall-clock saving is smaller, because diagrams already render in parallel across your cores – expect roughly a third off a diagram-heavy build, and very little on a site with a handful of them.

Entries are grouped by a fingerprint covering your models, theme, plugins, and render settings, so changing any of those renders fresh diagrams rather than serving ones made under the old settings.

The previous group is not deleted for you. Accent never removes anything from this directory on its own, which means editing a theme leaves the old generation behind as dead weight: run accent cache clear to reclaim it. A corrupt or truncated entry is re-rendered rather than served.

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("mermaid", ...) in a template
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) and the processed-media cache (media.cache.directory); pass --diagrams or --media to empty only one of them. The command is idempotent and safe to run while the server is stopped or running. For a running server, follow up with a configuration reload – pkill -HUP accent, or POST /_admin/reload on Windows – so the live process drops its in-memory diagram cache and forgets the media variants whose files are gone.
  • 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