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.
Related
- 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.