State diagrams

stateDiagram-v2 opens a state diagram. There is no in-diagram direction keyword; the author default is top-to-bottom. With a Viewport, responsive layout may flip wide diagrams to horizontal so they use available width (same rules as flowcharts). Without a viewport, layout stays vertical.

MermoidAscent.svgDiagram(orderFsm)
payment capturedcustomer cancelscarrier acceptsscanPendingCancelledPaidShippedDelivered

Transitions

From --> To declares a transition; : label names it. States do not need declaring — every id mentioned by a transition becomes a state, rendered as a Round node labelled with its own id.

MermoidAscent.svgDiagram("""stateDiagram-v2
    Idle --> Running: start
    Running --> Idle: stop
""".stripMargin)
startstopRunningIdle

That is a two-state cycle, and it lays out rather than looping forever — layering breaks cycles.

Start and end

[*] is the start/end pseudo-state: a filled 16×16 circle carrying the start-end class, with no label. Whether it reads as start or end is positional: [*] --> A versus A --> [*].

When a diagram uses both, mermoid paints two markers (start keeps id [*], end is [*]-end) so ranking does not cycle through a shared node and flip the layout. A diagram that only has one role still uses a single [*] node.

{
  import _root_.mermoid.*
  MermaidParser.parse("stateDiagram-v2\n  [*] --> A\n  A --> [*]\n").map(SvgRenderer.render(_)) match
    case Right(svg) =>
      (
        svg.sliding("start-end".length).count(_ == "start-end"),
        svg.contains("""id="node-[*]""""),
        svg.contains("""id="node-[*]-end""""),
      )
    case Left(_) => (-1, false, false)
}
(3,true,true)

Notes

note right of Idle
  waiting for work
end note

right of and left of are both supported. Note text is multi-line; each line is trimmed and blank lines dropped. A note renders as a dashed box joined to its state by a dashed connector, and the diagram's bounding box grows to hold it, including shifting the whole diagram right when a left of note would otherwise fall outside the canvas.

When the preferred side would overlap another node (common in horizontal / flipped layouts), the placer tries the other side and then a vertical offset before settling.

MermoidAscent.svgDiagram("""stateDiagram-v2
    [*] --> Idle
    Idle --> Running: start
    Running --> Idle: finish
    note right of Idle
      no work in flight
      polls every 5s
    end note
    note left of Running
      at most one job
    end note
""".stripMargin)
startfinishRunningIdleno work in flightpolls every 5sat most one job

Note text alignment

style <state> noteAlign: left | center | right sets how that state's note text is aligned. The default is left.

MermoidAscent.svgDiagram("""stateDiagram-v2
    [*] --> Ready
    Ready --> Done: go
    style Ready noteAlign: center
    note right of Ready
      centered
      note text
    end note
""".stripMargin)
goReadyDonecenterednote text

Note aliases

Like edges, notes take as <name> to pin their element id. Without it a note is note-{stateId}-{index}, so adding an earlier note on the same state renumbers the later ones.

{
  import _root_.mermoid.*
  MermaidParser
    .parse("stateDiagram-v2\n  A --> B\n  note right of A as caveat\n    careful\n  end note\n")
    .map(SvgRenderer.render(_))
    .map(_.contains("""id="note-caveat""""))
}
Right(true)

Self-transitions

A state can transition to itself, and stacked self-transitions stack their labels. The diagram's height accounts for the loops, and for notes pushed below them.

MermoidAscent.svgDiagram("""stateDiagram-v2
    [*] --> Retrying
    Retrying --> Retrying: attempt failed
    Retrying --> Retrying: backoff elapsed
    Retrying --> Done: succeeded
""".stripMargin)
attempt failedbackoff elapsedsucceededRetryingDone

Not yet implemented

Composite (nested) states, concurrency (--), an in-diagram direction, and state X as "long name" declarations are not implemented. click is flowchart-only; state diagrams have no click statement. A state diagram that needs nesting can be expressed as a flowchart with subgraphs today.