Skip to content

Diagrams

Write a mermaid diagram in a plain fenced block and it comes out as a picture:

```mermaid
flowchart LR
    Draft --> Review --> Published
```

Nothing marks it for Keystone, so GitHub and your editor still preview it as a diagram — the file reads the same wherever it goes.

Note

This needs the diagram hook, which any template can wire in and core-diagrams ships already wired. Without it the same fence renders as a code listing — see Where diagrams come from.

Sizing, alignment and cross-references

A bare fence renders at the full width of your text. To size it, place it, or refer to it later, wrap it in aligned-figure:

::: {#fig-flow .aligned-figure width=80% align=center}
```mermaid
flowchart LR
    Draft --> Review --> Published
```
:::

The stages are set out in [the workflow diagram](#fig-flow).

A rendered diagram is a figure, so sizing, placement and cross-references work exactly as Figures & images describes. Captions are the one difference.

Tip

If you write many of them, name the wrapper for what it holds in your project's shortcuts.yaml, and give it the width your book uses:

diagram:
  class: aligned-figure
  interface:
    width:
      bind: class.width
      default: 80%

A chain inherits its parent's interface, so ::: {.diagram} still takes every attribute aligned-figure does.

Captions

The caption comes from the diagram's own title, in mermaid's frontmatter:

```mermaid
---
title: From draft to published
---
flowchart LR
    Draft --> Review --> Published
```

That keeps one thing in one place — the title travels with the diagram into any other tool that reads mermaid. It becomes the figure's caption, so it lands below the diagram in the book's own caption style rather than being drawn inside the picture. A diagram with no title gets no caption.

Styling

How a diagram looks is mermaid's to decide, and you steer it from inside the block with mermaid's own init directive. Keystone reads nothing inside the block, so it reaches the renderer as you wrote it.

A misspelled directive stops the build, naming the line. Mermaid itself would skip it in silence, handing you a diagram that renders and is quietly not the one you asked for.

Picking a theme

The quickest change is a whole palette:

```mermaid
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    Client->>Server: POST /build
    Server-->>Client: 202 accepted
```
Theme
default mermaid's own — lavender nodes, used when you name none
neutral greys and thin borders
base uncolored, meant to be driven by themeVariables below
forest greens
dark light strokes for a dark background

neutral is the one to reach for in a book: it prints well in black and white and sits beside body text without competing with it.

What each theme draws shows the same flowchart at each of them, rendered by the diagram renderer itself.

Changing individual colors

base plus themeVariables sets colors one at a time, which is how a diagram is matched to a book's palette:

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {
    'primaryColor': '#eeeeee',
    'primaryBorderColor': '#999999',
    'lineColor': '#333333',
    'fontFamily': 'Georgia, serif'
}}}%%
flowchart LR
    Draft --> Review --> Published
```

Mermaid's theme variables lists every name. fontFamily is the one to be careful with — see Matching the book for which faces a diagram can actually be lettered in.

Layout

The same directive carries per-diagram layout settings, which differ by diagram type — curve for a flowchart's edges, mirrorActors for a sequence diagram:

```mermaid
%%{init: {'flowchart': {'curve': 'linear'}}}%%
flowchart LR
    Draft --> Review
```

Matching the book

A house style in project.conf is written once rather than in every fence:

KEYSTONE_DIAGRAMS_THEME=neutral
KEYSTONE_DIAGRAMS_FONT=Noto Serif, serif
KEYSTONE_DIAGRAMS_LOOK=classic
KEYSTONE_DIAGRAMS_LAYOUT=dagre

A single diagram can still name its own, and one that does wins.

The font is the renderer's, not your book's. Diagrams are lettered in another container, which carries its own faces — Noto Sans, Noto Serif, Noto Sans Mono and Open Sans among them. Name one of those, and end the stack with a generic family so anything else still lands somewhere sensible. Unset, the stack is Noto Sans, sans-serif; Noto Serif, serif is what gets a serif book most of the way.

KEYSTONE_DIAGRAMS_LOOK=handDrawn sketches every edge and box, which suits a book that wants its diagrams to read as provisional. classic is the default and draws them straight.

KEYSTONE_DIAGRAMS_LAYOUT=elk places nodes with the Eclipse Layout Kernel; dagre is the default. What each setting draws shows the same flowchart under both.

No house style carries lettering size. The wrapper scales the figure, so the same diagram at width=80% and at width=40% letters at two sizes. What you give it for a width is what decides how large its text reads.

Anything finer belongs to the block it applies to, and to that block alone — not even a shortcut of your own can carry it.

What ends up in the book

Each format gets the picture that format can use: a high-resolution raster for PDF, DOCX and ODT, and SVG for EPUB, which scales to the reader's page.

A diagram is drawn in one palette, whichever format it lands in and whatever screen it is read on. The picture paints its own background, so it stays legible on a page the reader has themed. A book that wants dark diagrams sets the theme to dark.

Showing the source instead

To print the mermaid source as a code listing — when the thing you are writing about is the diagram source — put another class in front of it:

```{.text .mermaid}
flowchart LR
    Draft --> Review --> Published
```

The first class is the language, as everywhere in Pandoc, so the block is text that happens to contain mermaid.

Where diagrams come from

A diagram is rendered by a hook — a container the compose file wires in beside the engine, which core-diagrams ships already wired. The engine itself knows nothing about mermaid. The hook is published on its own: Available hooks names it, and Wiring a published hook puts it in any template.

In a template with no such hook, a mermaid fence is a fence like any other and renders as code — no error, no missing figure. A diagram written into a shortcut definition renders as code too: definitions are expanded after hooks have run.

When a diagram won't build

A diagram mermaid cannot read stops the build. The message names the block by its opening, and mermaid's own account of the fault follows behind the quote bar:

ERROR: hook 'diagrams' on a 'mermaid' block starting "flowchart LR Draft --|
  Review": mermaid could not parse this diagram
  │ Parse error on line 3:
  │ ...  Draft --| Review
  │ ---------------------^
  │ Expecting 'LINK', 'UNICODE_TEXT', 'EDGE_TEXT', got '1'

A hook that fails covers the rest of what can go wrong, including a renderer that was never started.

  • Figures & images — sizing, captions and cross-references, which work the same here.
  • core-diagrams — the template that ships the renderer.
  • Hooks — what a hook is, how one is wired, and what a wired one leaves running.