State diagrams
A state_diagram draws an entity lifecycle coordinate-free: states auto-rank by longest path from the transition graph (back-edges and self-loops route around), initial draws the filled entry dot, final the double border, and each transition carries a trigger [guard] edge label. It is a page-level block — not a diagram shape.
state_diagram {
width = 640
direction = :left_to_right
state "pending" { name = "Pending" initial = true }
state "paid" { name = "Paid" }
state "shipped" { name = "Shipped" final = true }
state "cancelled" { name = "Cancelled" final = true }
transition "t1" { from = "pending" to = "paid" trigger = "payment captured" }
transition "t2" { from = "paid" to = "shipped" trigger = "dispatched" guard = "stock reserved" }
transition "t3" { from = "pending" to = "cancelled" trigger = "customer cancels" }
transition "t4" { from = "paid" to = "paid" trigger = "partial refund" }
}
Layout
States rank along the flow axis (direction, default :toptobottom) by longest path from the graph's roots; states sharing a rank stack along the cross axis in declaration order. A state with explicit x and y opts out of auto-layout for the rare manual case. layer_gap / node_gap tune the spacing.
Blocks
state_diagram
| Property | Type | Required | Description |
|---|---|---|---|
| width | f64 | no | Rendered width in pixels; the height follows the content. |
| direction | symbol | no | Flow direction: :toptobottom (default) / :lefttoright. |
| layer_gap | f64 | no | Spacing between ranks (layers). |
| node_gap | f64 | no | Spacing between states within a rank. |
| id | identifier | no | Optional explicit HTML id. |
| class | list<utf8> | no | Optional style classes on the <svg>. |
| desc | utf8 | no | Accessible description (aria-label + <title>). |
Child blocks
| Slot | Accepts | Multiple | Description |
|---|---|---|---|
| states | state | yes | States; auto-ranked from the transition graph in declaration order. |
| transitions | transition | yes | Transitions between states (trigger [guard] edge labels). |
state
| Property | Type | Required | Description |
|---|---|---|---|
| id | utf8 | yes | Stable id transitions reference via from / to. |
| name | utf8 | no | Display name shown in the box (defaults to the id). |
| initial | bool | no | Entry state: draws the filled-dot pseudo-state with an arrow into the box. |
| final | bool | no | Final state: draws the double-border marker. |
| link | utf8 | no | Link the box to an in-site page (bare page name, or site:page). |
| x | f64 | no | Manual x placement (with y, opts this state out of auto-layout). |
| y | f64 | no | Manual y placement (with x, opts this state out of auto-layout). |
| width | f64 | no | Box width. |
| height | f64 | no | Box height. |
| class | list<utf8> | no | Style classes for the box (replaces the theme defaults). |
transition
A transition is a block (not an a -> b connection) because it carries payload: the trigger event and an optional guard, rendered as the standard trigger [guard] label at the edge midpoint. The same from and to renders a self-loop arc.
| Property | Type | Required | Description |
|---|---|---|---|
| id | utf8 | yes | Stable id. |
| from | utf8 | yes | Source state id. |
| to | utf8 | yes | Destination state id (same as from for a self-loop). |
| trigger | utf8 | no | The event that fires the transition (the edge label). |
| guard | utf8 | no | Guard condition, rendered as trigger [guard]. |
Data-driven lifecycles
Like every @children slot, states / transitions accept computed splices — a state-machine model can generate its figure:
state_diagram {
states = map(machine.states, fn(s: SmState) -> State { { id: s.key, name: s.label } })
transitions = map(machine.edges, fn(e: SmEdge) -> Transition {
{ id: e.key, from: e.src, to: e.dst, trigger: e.event }
})
}