Animate the flow
This guide shows you how to choreograph a diagram's animation — packets, bursts, status pills, and persistent effects — so the motion tells a story rather than just decorating the page.
Flow works the same across every diagram type. A packet rides a connector; a step like status or fail marks a box. Wherever a step below takes a node, that means any box — an architecture node, a state, a sequence participant, or a class card — and wherever a step takes a connector's from/to, that connector may be an edge, a message, a transition, or a relation. Only ids matter, so a state id or participant id works anywhere a node id does. The examples here happen to be architecture diagrams; the vocabulary carries over unchanged.
Each recipe below is a small live diagram; for the exhaustive list of steps and knobs, see the flow & animation reference.
Let Beck derive a flow
If you write no flow block at all, Beck animates anyway — and what it derives fits the diagram type: architecture and state diagrams get a topological packet-walk from roots to leaves; a sequence plays its messages in authored order. Class diagrams are the exception: they're structural reference material with no sequence of events to play, so they render a still frame unless you script a flow: yourself. Declaring the boxes and connectors is enough to get motion — see derived flow for the exact behaviour per type.
type: architecture
meta: { title: Auto flow, direction: LR }
nodes:
- { id: client, title: Client, kind: user }
- { id: api, title: API, kind: gateway }
- { id: db, title: Postgres, kind: db }
edges:
- { from: client, to: api }
- { from: api, to: db, label: queries }
Reach for an explicit flow when topological order does not match the story you want to tell.
Add a flow block
A flow block has steps — an ordered list where each step is a single-key map — plus repeat and repeatDelay. Set repeat: -1 to loop forever (the default), or repeat: 0 to play once. repeatDelay is the pause in seconds before the loop restarts.
The workhorse step is packet, which sends one dot along an edge. Give it a from, a to, and optionally a label and color.
type: architecture
meta: { title: Request, direction: LR }
nodes:
- { id: client, title: Client, kind: user }
- { id: api, title: API, kind: gateway }
- { id: db, title: Postgres, kind: db }
edges:
- { from: client, to: api }
- { from: api, to: db }
flow:
repeat: -1
repeatDelay: 1.5
steps:
- packet: { from: client, to: api, label: GET /item }
- packet: { from: api, to: db, label: SELECT, color: info }
- packet: { from: db, to: api, color: success }
Note
meta.loop: false forces flow.repeat: 0, so the sequence plays once and stops. meta.animate: false skips the runtime entirely and renders a static frame.
Shape a packet
Every packet inherits motion from its edge's kind — size, speed, glow, and ease — but you can override any of it. Set shape to dot (the default, keeping the edge-kind size), circle, or ring; tune size, speed, glow, ease, and impact (an expanding ring on arrival). The example below sends a slow, decelerating ring that lands with impact.
type: architecture
meta: { title: Shaped packet, direction: LR }
nodes:
- { id: ingest, title: Ingest, kind: gateway }
- { id: store, title: Warehouse, icon: warehouse, kind: db }
edges:
- { from: ingest, to: store }
flow:
steps:
- packet: { from: ingest, to: store, shape: ring, ease: decelerate, size: 14, speed: 220, glow: true, impact: true, color: info }
See the flow reference for the full list of knobs and ease names.
Send a burst
A burst fires several packets in waves. Set count for the number of waves and stagger for the gap between them. The clever part: to can be a list, so one source fans out to many targets at once.
type: architecture
meta: { title: Fan-out, direction: TB }
nodes:
- { id: broker, title: Broker, kind: queue }
- { id: a, title: Worker A }
- { id: b, title: Worker B }
- { id: c, title: Worker C }
edges:
- { from: broker, to: a }
- { from: broker, to: b }
- { from: broker, to: c }
flow:
steps:
- burst: { from: broker, to: [a, b, c], count: 4, stagger: 0.1, color: warn }
Each wave broadcasts a dot to every target in the list, so count: 4 over three workers sends twelve dots in total.
Mark nodes
State changes carry as much story as movement. Several steps annotate a box directly — a node, state, participant, or class card:
statussets a persisting pill on the node.workingleaves a node breathing (busy) until you clear it withidleorreset.highlightandpulsedraw a brief eye to a node.failshakes the node red and flashes, with optional statustext.
Here is a tiny build-then-fail story: the build node goes to work, the tests run, and the deploy fails.
type: architecture
meta: { title: CI run, direction: LR }
nodes:
- { id: build, title: Build, icon: code }
- { id: test, title: Tests, kind: service }
- { id: deploy, title: Deploy, kind: gateway }
edges:
- { from: build, to: test }
- { from: test, to: deploy }
flow:
steps:
- working: { node: build }
- packet: { from: build, to: test, label: artifact }
- idle: { node: build }
- status: { node: build, text: built, color: success }
- pulse: { node: test }
- packet: { from: test, to: deploy }
- fail: { node: deploy, text: rollout failed }
Persistent edge effects
Two steps change a connector — an edge, message, transition, or relation — until you reset, which is ideal for showing an established connection rather than a one-off message:
activaterecolours a path and keeps it lit.streamruns continuous flowing dashes along a path for ongoing traffic.
type: architecture
meta: { title: Live link, direction: LR }
nodes:
- { id: producer, title: Producer, kind: service }
- { id: topic, title: Topic, icon: kafka, kind: queue }
- { id: consumer, title: Consumer, kind: service }
edges:
- { from: producer, to: topic }
- { from: topic, to: consumer }
flow:
steps:
- activate: { from: producer, to: topic, color: success }
- stream: { from: topic, to: consumer, color: info }
- wait: 2
- reset:
Narrate the story
Motion shows what moves; a caption says why. A narrate step writes a line to a caption bar
under the diagram and holds it long enough to read before the flow moves on — a teleprompter that
walks the viewer through the animation in words. Drop a narrate between the steps it explains:
type: architecture
meta: { title: Nightly report, direction: LR }
nodes:
- { id: cron, title: Scheduler, kind: service, icon: clock }
- { id: worker, title: Report Worker, kind: service }
- { id: db, title: Warehouse, kind: db, icon: warehouse }
- { id: mail, title: Email, kind: external, icon: mail }
edges:
- { from: cron, to: worker }
- { from: worker, to: db }
- { from: worker, to: mail }
flow:
steps:
- narrate: At midnight the scheduler wakes the report worker.
- packet: { from: cron, to: worker, label: run }
- narrate: The worker pulls last night's numbers from the warehouse.
- working: { node: db }
- packet: { from: worker, to: db, color: info }
- packet: { from: db, to: worker, color: success }
- idle: { node: db }
- narrate: { text: "With the figures in hand, it emails the finished report.", color: success }
- packet: { from: worker, to: mail, label: send }
- wait: 1.4
The shorthand narrate: <text> is all you usually need — the caption's hold time is computed from
its length, so a longer line lingers longer. When you want more control, use the full form and set
hold (seconds, overriding the auto pace) or color (an accent token or CSS colour that tints the
line):
- narrate: The worker pulls last night's numbers from the warehouse.
- narrate: { text: The rollout failed — reverting., hold: 3, color: danger }
Note
Captions are prose, so they often contain commas. The bare narrate: <text> shorthand handles
them fine, but inside the braced { } form — or any inline note: — you must quote text with a
comma (text: "Approved, and live."), or YAML reads the comma as the end of the value.
You don't have to write a flow at all to get narration. When Beck derives a
flow, a note: on the connector becomes the caption for that
hop — a note on an architecture edge, a sequence message,
or a state transition. It's the quickest way to caption a diagram that already animates itself:
edges:
- { from: worker, to: db, note: The worker pulls last night's numbers. }
Tune the reading pace once, for the whole diagram, with meta.narrate — a mapping of wpm
(reading speed), min (a floor on each caption's time), and pad (extra lead-in/out). Set
meta.narrate: false to drop the caption bar entirely. See the narration
reference for the exact knobs and defaults.
Control the sequence
A handful of steps shape timing rather than visuals:
parallelruns a list of steps at the same instant.waitpauses for a number of seconds (default0.5).phasedrops a namedseek()label you can jump to from script.resetrestores the diagram to its initial state.
type: architecture
meta: { title: Read path, direction: LR }
nodes:
- { id: client, title: Client, kind: user }
- { id: api, title: API, kind: gateway }
- { id: cache, title: Redis, kind: cache }
- { id: db, title: Postgres, kind: db }
edges:
- { from: client, to: api }
- { from: api, to: cache }
- { from: api, to: db }
flow:
steps:
- phase: lookup
- packet: { from: client, to: api, label: GET /item }
- parallel: [ { packet: { from: api, to: cache, color: warn } }, { working: { node: db } } ]
- status: { node: cache, text: miss, color: warn }
- packet: { from: api, to: db, label: SELECT }
- idle: { node: db }
- packet: { from: db, to: api, color: success }
- wait: 1
- reset:
Respect reduced motion
When a visitor has prefers-reduced-motion set, or you author meta.animate: false, Beck renders the static frame instead. Write your flow for the animated case — the static fallback is automatic, so you never script two versions.
Next steps
- The flow & animation reference is the complete vocabulary: every step type, every packet knob, and the per-edge-kind motion defaults.
- Each diagram type's guide has a short note on flow for that type — sequence, state, and class.
- To script a flow from C# instead of YAML, see generate diagrams from your code and the Beck.Authoring API.
- Try a sequence live in the playground.