Mermaid statechart diagrams: advanced state management
State machines are everywhere: user authentication, order processing, payment workflows, UI component lifecycles. Mermaid's statechart syntax (stateDiagram-v2) handles the complex patterns that simple state diagrams can't — nested states, parallel regions, guards, and actions that real systems need.
Unlike a flowchart that models steps, a statechart models behavior — what happens when an event arrives, which transitions are valid in which state, and what happens on entry or exit. It's the right tool for workflow automation, game logic, IoT systems, and any state machine you'd sketch on a whiteboard.
States and simple transitions
Every statechart starts with a state (a box) and transitions (arrows labeled with events):
stateDiagram-v2
[*] --> Idle
Idle --> Loading: fetch()
Loading --> Idle: error
Loading --> Ready: success
Ready --> Idle: reset()
Ready --> Saving: save()
Saving --> Ready: done
Ready --> [*]
[*] is the start and end pseudo-state. The arrow labels (fetch(), error, etc.) are events — things that trigger transitions. When the system is in Idle and receives a fetch event, it moves to Loading. From Loading, an error event goes back to Idle, or success moves to Ready.
Nested states (composite states)
Real workflows have sub-workflows. A payment processor might have an Active state that contains Pending, Processing, and Settled sub-states. Use nested state blocks:
stateDiagram-v2
[*] --> Offline
Offline --> Online: connect
Online --> Offline: disconnect
state Online {
[*] --> Idle
Idle --> Authenticating: login
Authenticating --> Authenticated: success
Authenticating --> Idle: fail
Authenticated --> Idle: logout
}
When Online, the system starts in the nested Idle state. Events like login only matter inside Online — if you're Offline, login has no effect (unless you define it at the outer level). Nesting lets you hide complexity and reuse state names in different scopes.
Guards (conditional transitions)
Some transitions should only happen if a condition is true. Use guards with [condition] syntax:
stateDiagram-v2
[*] --> Cart
Cart --> Checkout: proceed
state Checkout {
[*] --> Payment
Payment --> Processing: [balance >= total]
Payment --> Declined: [balance < total]
Processing --> Success: authorized
Declined --> Cart: retry
}
Success --> [*]
The transition from Payment to Processing only fires if the guard [balance >= total] is true. If false, that transition isn't available — only [balance < total] applies. Guards let you encode business logic into the state machine itself.
Actions (entry, exit, activities)
States often trigger side effects: start a timer, send a notification, cancel a request. Use entry and exit actions:
stateDiagram-v2
[*] --> Idle
Idle --> Loading: fetch
state Loading {
entry / startSpinner()
exit / stopSpinner()
}
Loading --> Ready: success
Loading --> Error: fail
state Error {
entry / logError()
}
Error --> Idle: retry
Ready --> [*]
When entering Loading, startSpinner() runs. When exiting (to Ready or Error), stopSpinner() runs. This maps to real code: in a React component, entry actions might trigger useEffect, and exit actions might clean up.
Parallel regions (concurrent states)
Some systems do multiple things at once. For example, a music player might be simultaneously Playing and have a Volume level. Statecharts support parallel regions with --- dividers:
stateDiagram-v2
[*] --> Player
state Player {
state PlaybackControl {
[*] --> Stopped
Stopped --> Playing: play()
Playing --> Stopped: stop()
Playing --> Paused: pause()
Paused --> Playing: resume()
}
---
state VolumeControl {
[*] --> Normal
Normal --> Muted: mute()
Muted --> Normal: unmute()
}
}
Player --> [*]
Inside Player, both PlaybackControl and VolumeControl are active at the same time. You can pause the music while it's muted, or unmute while it's stopped — they're independent. This models real-world concurrency without the complexity of flowchart arrows crossing everywhere.
Comparison: statechart vs state diagram vs flowchart
| Feature | Flowchart | State Diagram | Statechart |
|---|---|---|---|
| Models | Steps/processes | Simple states/transitions | Complex behavior with guards/actions |
| Nesting | No | No | Yes (composite states) |
| Guards | No | No | Yes |
| Entry/exit actions | No | No | Yes |
| Parallel regions | No | No | Yes |
| Best for | "How to do X" | "What are the valid states?" | "How does system Y behave over time?" |
A flowchart tells a story (step 1, then 2, then 3). A state diagram catalogs states (these are the valid configurations). A statechart models transitions (if in state X and event Y arrives, do Z).
Real-world examples
E-commerce order:
stateDiagram-v2
[*] --> New
New --> Confirmed: pay()
Confirmed --> Paid: [payment_verified]
state Paid {
entry / reserveInventory()
[*] --> AwaitingShip
AwaitingShip --> Shipped: ship()
Shipped --> Delivered: deliver()
}
Paid --> Returned: [within_return_window]
Returned --> Refunded: refund()
Refunded --> [*]
Delivered --> [*]
Game character state:
stateDiagram-v2
[*] --> Alive
state Alive {
state Movement {
[*] --> Idle
Idle --> Moving: move()
Moving --> Idle: stop()
}
---
state Combat {
[*] --> Defending
Defending --> Attacking: attack()
Attacking --> Defending: defend()
}
}
Alive --> Dead: [health <= 0]
Dead --> [*]
The character can move and fight independently. Movement doesn't interrupt combat; they're parallel concerns.
Tips for clear statecharts
-
Name states by outcomes, not actions — "Authenticated" not "Authenticating" (the latter is an activity, not a state). Use entry/exit actions for what happens during transitions.
-
Keep guard conditions simple — if a guard spans more than a few words, extract it to a method and reference the name.
-
Avoid over-nesting — three levels deep is usually the limit. If a composite state needs more than 5 sub-states, split it into separate diagrams.
-
Test edge cases — for every state, ask "what happens if an unexpected event arrives?" Design guards and transitions to handle them gracefully.
-
Link to code — a statechart is documentation. In your implementation (Redux, XState, a custom state machine), the states and events should map 1:1.
Rendering and exporting your statecharts
Statecharts are complex, so render them as you go. Paste the examples above into MermaidCreator's editor, tweak them, and see the diagram update live. Export as PNG or SVG when you're ready to share.
For production workflows, libraries like XState let you write statecharts in code and enforce type safety. Mermaid's syntax is the sketching language; XState is the implementation language.
FAQ
Can I use stateDiagram-v2 for everything?
No. Statecharts are overkill for a simple list of states. Use a flowchart for processes, a state diagram for simple state catalogs, and a statechart when you need nesting, guards, and actions.
What's the difference between stateDiagram-v2 and the older stateDiagram?
v2 is the current standard and supports nested states and parallel regions. Use v2 — the old syntax is deprecated.
Can I send multiple events at once? No. Statecharts process events sequentially. If two events arrive at the same time, the system queues them and processes one per cycle.
How do I model timeouts?
Use a named activity in an entry action: entry / startTimer(5s). When the timer fires (after 5 seconds), it generates a timeout event. This is a convention — implement it in your state machine library.
Build your next workflow as a statechart first, sketch it in MermaidCreator, then implement with confidence.
Related posts
Build team collaboration workflows with Mermaid
Document approval processes, cross-team handoffs, and decision gates with diagrams that every team member can read and update.
Mermaid subgraphs and swimlanes for complex workflow diagrams
Organize multi-actor workflows with swimlanes and break large diagrams into logical clusters with subgraphs. Master Mermaid's most powerful layout tools.