state-chart
Native state machine diagram — states as a numbered list, transitions as nested inline-code refs.
Use to show a finite-state machine — the discrete states a system can be in and the events that move between them. Authors write a numbered list; each state's index becomes its stable ref so transitions cite numbers, not names. The numbering is the REF, always: transitions cite => 4, and the index badge is painted on the node. It is also the LAYOUT for a chain, where a single column is not an approximation of good layout but the right answer. A machine that branches is re-ranked by dagre, because no column can show a fan-out — the states would read as a sequence.
When to use
- Finite, named states with discrete events. When the slide is about a system with a small set of named places it can be in (Draft / Submitted / Approved / Archived) and the events that move between them (submit, approve, reject). The numbered authoring forces you to enumerate every state up front; the inline refs force you to be explicit about every transition.
- Sequential authoring as a forcing function. Numbering the states makes the author commit to an order. Reading the list top-to-bottom is reading the machine from start to terminal. This is the same forcing function that
list-stepsandagendaapply — a sequence the reader can scan in one pass. - Native theming without Mermaid overhead. Mermaid's
stateDiagram-v2works but requires a CSS override cascade with!importantto theme cleanly (seedocs/theming.md). A native state chart uses palette tokens directly — no overrides, no mmdc subprocess, no version-coupled SVG class names.
When not to use
- More than ~8 states. Vertical stacks of ten or more states stop reading as a machine and start reading as a list. If the system has many states, group them into phases and show one phase at a time, or step back to a higher-level abstraction. The chart's job is to make the topology obvious in one glance.
- Hierarchical or parallel states. v1 grammar is one flat list of states with one outgoing arrow per nested bullet. Composite states, orthogonal regions, history nodes — anything Mermaid's
stateDiagram-v2does and this layout doesn't — belong in a Mermaid fence via thediagramcomponent. - Continuous processes. If the diagram is really a workflow with stages that overlap or block (queue depth, throughput, capacity), a
ganttorkanbanchart reads better. State charts are for discrete, mutually-exclusive states the system flips between.
Common mistakes
- Writing a transition's event/target as plain text instead of a single inline-code arrow. The transition/detail distinction is purely mechanical — a nested bullet whose SOLE content is one inline-code token matching
event => Nor=> N(N a digit orself) is a transition; anything else — plain text, or an inline-code token with a non-numeric target like `approve => Approved` — is captured as detail prose instead. A transition written as plain text (or with a named target) is silently treated as detail, not as an edge, and no arrow renders. - Using a state's NAME instead of its numeric index as a transition target (`
approve => Approvedinstead ofapprove => 4`). Transitions target the state's INDEX — its position in the numbered list, which is the stable ref — not its name; a name in the target position won't resolve to any state.
Authoring
<!-- _class: state-chart -->
`Submission lifecycle`
## Document approval flow.
How a draft moves from author to archive.
1. Draft `start`
- `submit => 2`
- `discard => 6`
2. Submitted `on-track`
- `review => 3`
3. In Review
- `approve => 4`
- `reject => 1`
- `revise => self`
4. Approved `done`
- `publish => 5`
5. Published `live`
- `archive => 6`
6. Archived `end`
*Rejected drafts return to the author; revisions stay in review.*Slots
| Slot | Selector | Required | Description |
|---|---|---|---|
title | h2 | yes | Slide heading framing the state machine. |
eyebrow | p > code | no | Optional eyebrow naming the machine or domain. |
states | ol > li | yes | One li per state. Index is the stable ref. Trailing inline code is a closed metadata vocabulary: start, end, or one of the chart-status keywords (on-track, at-risk, blocked, done, live, decision, deferred, warn, pilot, fail). Multiple metadata tokens allowed; order is irrelevant. Unknown trailing codes are left in the rendered label. |
transitions | ol > li > ul > li | no | Outgoing transitions from a state — one per nested bullet. Each carries a single inline-code arrow event=>N or =>N (event optional). Target is a state index or the literal self for self-loops. Whitespace inside the inline code is insignificant. The event text may carry explicit line breaks — a literal \n or an HTML <br> / <br/> — which are honored on any machine. On a BRANCHING machine (the one dagre re-ranks) the label also sits OFF the line — BELOW it on lr, to the RIGHT of it on the default — and text still too long for the rank gap wraps on its own there, never mid-word. A machine that lays out as a single column keeps its label ON the line under a halo, as it always has, and does not auto-wrap: a second line there would cut a gap out of the connector it labels. |
detail | ol > li > ul > li (prose, no arrow) | no | Optional per-state reveal detail (the shared chart-family detail substrate). A nested bullet under a state that is NOT an inline-code transition (plain prose — the entry/exit action, the rule, the why) is captured as that state's detail rather than a transition. It drives two surfaces from one source: (1) Present/Practice/Preview — the state node is tagged data-mark and the prose rides an inert <template class="chart-detail"> the reveal layer shows in a popover on hover/tap, with the active node lifted, the rest dimmed, and the whole figure tilting (the edge-router skips re-measuring while the tilt is live, so the routed edges stay aligned); (2) the static PDF — the same detail folds into the slide's speaker note (Label (status): item · item) as a Marp-faithful comment. Renders nothing on the slide face, so a machine with no prose bullets is byte-identical. Must be a bullet (-/*), not numbered. |
tint | ol > li (::: suffix), ol > li > ul > li (::: suffix) | no | Optional :::token naming a THEME TOKEN to paint with — :::state-fail-hue, never a color literal. On a state it tints the node's gradient and stroke; on a transition it tints the line and its arrowhead. A transition takes an optional second slot for its edge-label background: :::state-fail-hue/surface-raised. The name is used verbatim as var(--<token>) and must match ^[a-z][a-z0-9-]*$ with no -- prefix (the engine adds it); anything else is dropped whole and the element keeps its inherited paint. Existence is NOT checked at build time — this is a pure string transform and cannot read the theme's declared tokens — so a typo falls back to the untinted default and the deck degrades rather than breaking. Nothing names the typo today: it is silent on every surface, including lint:deck. |
Anatomy
┌─────────────────────────────────────────┐
│ header │
│ State machine heading │
│ │
│ [Draft ] → [Review] → [Pub ] │
│ │
│ (back-edge: Review → Draft) │
│ │
│ footer 1/19 │
└─────────────────────────────────────────┘Variant decision rule
- default (no modifier) — The default top-to-bottom vertical stack — the plainest read, no extra framing needed.
lr— The states read more naturally as a left-to-right flow (e.g. a pipeline direction) than top-to-bottom.inline— The chart needs to sit directly beside its explanatory prose rather than take the full canvas.curved— Eased, curved connectors fit the deck's visual tone better than straight arrows.
Variants
lr — Left-to-right
States flow left to right.
<!-- _class: state-chart lr -->
## lr flows the states left to right.
1. Source `start`
- `compile => 2`
2. Compiled
- `test => 3`
3. Tested
- `deploy => 4`
- `fail => 1`
4. Deployed `end`inline — Inline
The chart sits beside its prose.
<!-- _class: state-chart inline -->
## inline sets the chart beside its prose.
1. Connecting `start`
- `retry => self`
- `ok => 2`
- `fail => 3`
2. Connected `live`
- `disconnect => 1`
3. Failed `end`curved — Curved
Eased arrows between states.
<!-- _class: state-chart curved -->
## curved eases the arrows between states.
1. Draft `start`
- `submit => 2`
- `discard => 5`
2. In Review `at-risk`
- `approve => 3`
- `revise => self`
- `reject => 1`
3. Approved
- `publish => 4`
4. Published `live`
- `archive => 5`
5. Archived `end`Related
- diagramthe machine has hierarchical states, parallel regions, or guards that need Mermaid's full state-diagram grammar
- journeythe sequence is a user's path through tasks with mood / affect, not a system's discrete states
- timeline-listevents are points in time rather than transitions between named states
- list-stepsa linear procedure with no branching — state-chart is overkill if there are no choices to make
- roadmapparallel workstreams across phases, not a single machine's transitions