Mermaid state diagram editor
A state diagram shows the states a single thing can be in and the events that move it between them. Use one when the subject is a lifecycle — an order, a subscription, a document under review. The giveaway is that your labels are adjectives rather than verbs: pending, shipped, cancelled.
An order lifecycle with a terminal cancellation
The states are what an order is; the labels on the arrows are what happened to it. Note that Cancelled is reachable from three states but leads nowhere — that asymmetry is exactly the kind of thing a state diagram makes obvious and a flowchart hides.
stateDiagram-v2
[*] --> Pending: order placed
Pending --> Paid: payment captured
Pending --> Cancelled: customer cancels
Paid --> Packed: warehouse picks
Paid --> Refunded: payment reversed
Packed --> Shipped: carrier collects
Packed --> Cancelled: stock missing
Shipped --> Delivered: carrier confirms
Shipped --> Lost: no scan for 14 days
Delivered --> [*]
Refunded --> [*]
Cancelled --> [*]
Lost --> Refunded: claim approvedWorked examples
1. The minimum state machine
`[*]` is both the start and the end pseudo-state — which one it means depends on which side of the arrow it sits.
stateDiagram-v2
[*] --> Draft
Draft --> Published: publish
Published --> [*]2. Naming states that contain spaces
State ids cannot contain spaces, but `state "Label" as id` gives you a readable label with a safe id. This is the state-diagram equivalent of a quoted flowchart label.
stateDiagram-v2
state "Awaiting review" as review
state "Changes requested" as changes
[*] --> review
review --> changes: reviewer objects
changes --> review: author pushes fix
review --> [*]: approved3. Composite states
A state can contain its own state machine. Use this when a stage has meaningful internal steps that would otherwise clutter the top level — here, everything that happens inside Processing.
stateDiagram-v2
[*] --> Queued
Queued --> Processing: worker picks up
state Processing {
[*] --> Validating
Validating --> Transforming: schema ok
Transforming --> Writing: rows mapped
Writing --> [*]
}
Processing --> Succeeded: no errors
Processing --> Failed: exception thrown
Failed --> Queued: retry
Succeeded --> [*]4. Choice pseudo-states
A `<<choice>>` is a branch that depends on a condition rather than an event. It keeps the decision visible without pretending it is a state the object rests in.
stateDiagram-v2
state check <<choice>>
[*] --> Submitted
Submitted --> check: run risk scoring
check --> Approved: score < 40
check --> ManualReview: score >= 40
ManualReview --> Approved: analyst accepts
ManualReview --> Rejected: analyst declines
Approved --> [*]
Rejected --> [*]5. Concurrent regions
Two dashes on their own line split a composite state into regions that are active at the same time. This is the one thing a state diagram does that a flowchart genuinely cannot.
stateDiagram-v2
[*] --> Onboarding
state Onboarding {
[*] --> EmailUnverified
EmailUnverified --> EmailVerified: link clicked
--
[*] --> ProfileEmpty
ProfileEmpty --> ProfileComplete: form submitted
}
Onboarding --> Active: both complete
Active --> [*]State diagram syntax reference
Use `stateDiagram-v2` rather than `stateDiagram`. Both render, but v2 is the actively developed layout engine and handles composite and concurrent states far better.
| Syntax | Meaning |
|---|---|
| stateDiagram-v2 | Opens the diagram. `stateDiagram` still works but is the older layout. |
| [*] --> A | Initial state — the entry point. |
| A --> [*] | Terminal state. |
| A --> B | Transition with no trigger named. |
| A --> B: event | Transition labelled with the event that causes it. |
| state "Label" as id | Readable label with a space-free id. |
| state A { ... } | Composite state containing its own machine. |
| -- | Inside a composite state, splits it into concurrent regions. |
| state x <<choice>> | Condition-based branch point. |
| state f <<fork>> / <<join>> | Split into and merge from parallel transitions. |
| note right of A: text | Attach a note. Also `note left of`. |
| direction LR | Lay the machine out left-to-right instead of top-down. |
Six errors that actually break state diagrams
Reproduced against Mermaid 11.12.2. The first four stop the diagram rendering. The last two are worse: they render happily and give you a diagram that does not mean what you wrote.
What you see
Parse error, ending in: got 'INVALID'
Why
A hyphen in a state id. Kebab-case names are natural to reach for — in-progress, pre-approved — but the hyphen is read as the start of a transition arrow.
Fix
Use a single word or underscores for the id, and put the readable text in a quoted label.
stateDiagram-v2
[*] --> in-progress
in-progress --> DonestateDiagram-v2
state "In progress" as inProgress
[*] --> inProgress
inProgress --> DoneWhat you see
Parse error inside a composite state
Why
A composite state opened with `{` and never closed. The closing brace has to be on its own line.
Fix
Close the block.
stateDiagram-v2
[*] --> Outer
state Outer {
[*] --> InnerstateDiagram-v2
[*] --> Outer
state Outer {
[*] --> Inner
}What you see
Lexical error on line N. Unrecognized text.
Why
The concurrent-region separator written with the wrong number of dashes. It is exactly two, on their own line, inside a composite state. Three dashes is a different token entirely.
Fix
Use exactly `--`.
stateDiagram-v2
state Both {
[*] --> A
---
[*] --> B
}stateDiagram-v2
state Both {
[*] --> A
--
[*] --> B
}What you see
Parse error on line 1, ending in: got 'ID'
Why
A version suffix that does not exist. There is `stateDiagram` and `stateDiagram-v2`, and nothing else — `-v3` fails at the first line.
Fix
Use `stateDiagram-v2`.
stateDiagram-v3
[*] --> DraftstateDiagram-v2
[*] --> DraftWhat you see
It renders, but one state has silently become several boxes
Why
A space in a state id. Mermaid does not reject it, and it does not read the rest as a description — it creates a separate box for every word. Measured by reading the emitted state ids: `[*] --> Awaiting review` produces two states, `Awaiting` and `review`, and only the first is on the end of the arrow; the other just sits there unconnected. A three-word name gives three boxes, so the diagram quietly grows sideways. The description mechanism is real, but it needs a colon — `review: waiting for a reviewer` — which is what this mistake gets confused with.
Fix
Declare the state with `state "Label" as id` and always refer to it by the id.
stateDiagram-v2
[*] --> Awaiting review
Awaiting review --> DonestateDiagram-v2
state "Awaiting review" as review
[*] --> review
review --> DoneWhat you see
It renders, but the choice node is drawn as an ordinary state
Why
The `<<choice>>` declaration came after the transitions that use it. Mermaid creates the state the first time it is mentioned, and a later stereotype does not change what has already been created.
Fix
Declare pseudo-states before the transitions that reference them.
stateDiagram-v2
[*] --> check
check --> Approved
check --> Rejected
state check <<choice>>stateDiagram-v2
state check <<choice>>
[*] --> check
check --> Approved
check --> RejectedRendering notes
Measured against Mermaid 11.12.2 as this site runs it.
Both stateDiagram and stateDiagram-v2 render — which is a trap
A common piece of advice is that you must use `stateDiagram-v2` or nothing will draw. That is not true in 11.12.2: both keywords render without error. The difference is layout quality, particularly for composite and concurrent states, and there is no warning when you use the old one. If a composite state looks cramped or the arrows route oddly, check which keyword you opened with before you start rewriting the diagram.
Height grows about 108px per state
Three states render to a viewBox of roughly 52×462; forty states to 60×4680. As with flowcharts, the width barely moves — state machines grow downward. `direction LR` inside the diagram is the usual fix when a lifecycle is long but shallow.
Composite states are laid out independently
The inner machine of a composite state is sized on its own and then placed, which is why a single large composite can push the whole diagram much wider than the state count suggests. If one box dominates, promoting its contents to the top level and linking to a second diagram usually reads better than fighting the layout.
Labels are HTML, so PNG export re-renders
Like flowchart, class and ER diagrams, state labels are drawn inside an SVG `<foreignObject>`. Browsers refuse to rasterise that onto a canvas, so PNG export on this site re-renders the diagram with plain SVG text labels first. The PNG is correct and full-size; the label typography is very slightly different from the screen.
Theme changes colour, never layout
Default and dark themes produce an identical viewBox for the same source, so a state machine cannot reflow when the theme changes.
When to use something else
If your labels are verbs — validate, send, retry — you are describing a process, not a lifecycle, and a flowchart is the honest choice. The clearest signal is that you cannot answer the question "what is the thing that is in this state?"
If several components each have their own lifecycle and the interesting part is how they interact, one state diagram per component plus a sequence diagram for the interaction beats one enormous machine.
And if every state connects to every other state, the diagram will be a hairball no matter how it is drawn. That usually means the states are not really states but flags that combine freely — in which case a table of valid combinations communicates far more than a picture.
Other diagram types
Written by Dominik Malsch · Last updated: