Why this session exists
Every flowchart starts clean. Then someone adds "just one more box" ten times, and now it reads like a plate of noodles. That homework diagram you brought - the one that grew past 10 nodes - is today's patient. The cure is not a bigger monitor: it is grouping (subgraphs), visual meaning (classes), and knowing when one diagram should become two. Nova's map gets the full treatment too.
Subgraphs - boxes within boxes 7 min live
A subgraph draws a labeled container around a group of nodes. Readers scan the containers first and the boxes second - which means a 20-node diagram with 3 zones reads like a 3-node diagram. That is the whole trick.
LiveThe subgraph block3 min▶
A subgraph is three things: the keyword, an ID with an optional display title, and an end. Everything declared between them lives inside the box. Edges work exactly as before - including edges that cross the box wall, and edges attached to the subgraph ID itself.
flowchart LR
subgraph src[Sources]
ev[App events]
crm[CRM export]
end
subgraph plat[Platform]
ing[Ingestion] --> qa{Quality checks}
qa -->|pass| dw[(Warehouse)]
end
subgraph cons[Consumers]
bi[Dashboards]
ml[ML features]
end
ev -->|stream| ing
crm -.->|nightly| ing
dw --> bi
dw --> ml
- Always give the ID:
subgraph src[Sources]. The ID (src) is how you draw edges to the whole zone later -src --> platconnects box wall to box wall. - Declare inside, connect anywhere. A node's home is the subgraph where it is first declared. Cross-zone edges (like
ev --> ing) are fine and normal. - Nesting works: a
subgraphinside asubgraphdraws boxes within boxes - handy for a "Platform" zone containing "Streaming" and "Batch" sub-zones.
The architecture review that finally landed. A platform team presented the same 25-node diagram twice. Flat version: execs squinted, meeting drifted. Zoned version - Sources, Our platform, Downstream teams - the CFO pointed at the middle box and asked exactly the right cost question. Same nodes, three subgraphs, different meeting.
LiveDirection inside a subgraph2 min▶
Each subgraph can set its own flow with a direction statement on its first line - a top-down zone inside a left-right diagram, for example:
- The catch (docs say so too): if any node inside the subgraph links to a node outside it, the subgraph's own direction is ignored and it inherits the parent flow. Design zones as units first, wire them second.
- When it shines: a wide LR pipeline with one tall TB zone for a decision ladder or an approval chain.
Self-studyZone design - what makes a good subgraph2 min read▶
Subgraphs are a grouping tool, and groupings are claims: "these things belong together". Good claims to make:
- Ownership - one zone per team or system boundary. The best default for architecture diagrams.
- Lifecycle stage - raw / staged / served zones in a data platform.
- Trust boundary - inside the VPC vs outside; great for security conversations.
Bad claim: grouping by "stuff that happened to fit there". If you cannot name the zone in two words, it is not a zone. And 3-5 zones is the sweet spot - seven subgraphs is spaghetti with extra steps.
Styling - meaning through color 7 min live
Untrained color is decoration. Trained color is a legend: stream things pink, batch things pale, storage things dark - and suddenly readers decode your diagram before reading a single label. Mermaid's tool for this is classDef.
LiveclassDef + class - define once, apply everywhere3 min▶
A classDef names a reusable style. A class statement (or the ::: shorthand right on a node) applies it. One definition, any number of nodes - exactly like CSS.
flowchart LR ev[App events]:::stream -->|stream| ing[Ingestion] crm[CRM export]:::batch -.->|nightly| ing ing --> dw[(Warehouse)] dw --> bi[Dashboards] class dw storage linkStyle 1 stroke:#F59E0B,stroke-width:3px classDef stream fill:#D6246E,color:#FFEFF5,stroke:#8E1146 classDef batch fill:#FFEFF5,color:#8E1146,stroke:#D6246E classDef storage fill:#8E1146,color:#FFEFF5,stroke:#8E1146
| Statement | Does | Use when |
|---|---|---|
classDef name fill:...,stroke:... | defines a named style | always - your palette lives here |
class a,b,c name | applies it to listed nodes | styling many nodes at once |
a[Label]:::name | applies it inline | styling at the point of declaration |
style a fill:#FFEFF5 | styles ONE node directly | a true one-off (rare - prefer classes) |
linkStyle 1 stroke:#F59E0B | styles edge number 1 (0-based, in source order) | highlighting one critical edge |
classDef at the BOTTOM of the block, together. Content first, palette last - the next editor finds all styling in one place, and your diff stays readable when the palette changes.
LiveOne meaning per color - the discipline2 min▶
Class names should name MEANINGS (stream, batch, storage, deprecated), never looks (pinkBox, dark2). If a color means two things in one diagram, it means nothing.
The migration diagram that ran itself. A data team marked every legacy node with a deprecated class (pale fill, dashed stroke). Each migration PR moved one node out of the class. The diagram became the burndown chart - stakeholders watched the pale boxes disappear week by week, no status deck required.
Self-studyv11 expanded shapes - a bigger vocabulary3 min read▶
Mermaid v11.3+ added a second way to pick shapes: the @{ } attribute syntax. Instead of memorizing bracket gymnastics, you name the shape - and the catalog grew to 30+ shapes (see flowchart.html#expanded-node-shapes in the docs).
flowchart LR
raw@{ shape: cyl, label: "Raw zone" } --> job@{ shape: rect, label: "Transform job" }
job --> marts@{ shape: cyl, label: "Marts" }
job --> rules@{ shape: hex, label: "Quality rules" }
- Handy names:
cyl(database),diam(decision),stadium(start/end),hex(preparation),circle,rounded,rect. - When to use which style: brackets for the daily six shapes (they type faster),
@{ }when you need a shape brackets cannot make, or when generating mermaid programmatically. - Quote the label inside
@{ }- it is already a habit from Session 1, and here it is required whenever the label has spaces.
Interaction + big-diagram hygiene 6 min live
Two finishing skills: making nodes clickable, and knowing when a diagram has outgrown one page. The second one matters more - no amount of styling saves a 60-node hairball.
LiveClick - nodes that go somewhere2 min▶
The click statement attaches a link (or a JavaScript callback) to a node. The pattern for links: click nodeId "https://..." "tooltip", with an optional _blank target at the end to open a new tab.
- Links:
click dw "https://example.com/warehouse" "Open the docs"- the node becomes a hyperlink. - Callbacks:
click dw call myFunction()runs page JavaScript - for self-hosted pages and internal tools. - The GitHub caveat: GitHub strips click interactions when rendering fences, for security. Clicks work in mermaid.live, self-hosted pages (like this one), and most docs sites - treat them as a bonus layer, never as the only way to reach the information.
LiveWhen to split, how to link2 min▶
The split signal is not a node count - it is a question count. A good diagram answers one question ("how does data flow through Nova?"). When yours starts answering three, split it:
- Overview diagram: zones only, maybe 8-12 nodes, the map of maps.
- Detail diagrams: one per zone, as deep as needed. Each lives next to the code it describes.
- Link them:
clicklinks where supported, plain markdown links under the fence everywhere else. The overview links down; each detail links back up.
Practical threshold: past roughly 20 nodes, start looking for the seam. Past 30, you are overdue.
Self-studyLayout nudging - the polite fixes2 min read▶
You cannot move boxes by hand, but you can nudge the layout engine, in escalating order:
- Reorder declarations. Mermaid lays nodes out roughly in source order - moving the line that declares a node often untangles a crossing wire. Free, try it first.
- Flip direction. LR to TD (or the reverse) re-deals the whole hand. Also free.
- Invisible edges:
a ~~~ bdraws nothing but tells the engine "keep these ranked apart". The precision tool for two stubborn nodes that refuse to line up. - Longer arrows: extra dashes (
---->) request extra rank distance, as in Session 1.
Nova, zoned and styled ★ 8 min · follow along
We rebuild Session 1's map with everything from today: three subgraph zones, plus a three-class palette - stream, batch, storage - so the diagram explains its own workloads at a glance. Build it line by line in mermaid.live; the finished source is below.
flowchart LR
subgraph src[Sources]
ev[App events]:::stream
crm[CRM export]:::batch
end
subgraph plat[Platform]
ing[Ingestion] --> qa{Quality checks}
qa -->|pass| dw[(Warehouse)]:::storage
qa -->|fail| dlq[Quarantine]:::storage
end
subgraph cons[Consumers]
bi[Dashboards]
ml[ML features]
end
ev -->|stream| ing
crm -.->|nightly| ing
dw --> bi
dw --> ml
classDef stream fill:#D6246E,color:#FFEFF5,stroke:#8E1146
classDef batch fill:#FFEFF5,color:#8E1146,stroke:#D6246E
classDef storage fill:#8E1146,color:#FFEFF5,stroke:#8E1146
Start from your Session 1 source in mermaid.live. Wrap the two source nodes in subgraph src[Sources] ... end. Watch the container appear around them.
Zone the middle: move ingestion, quality checks, warehouse, and quarantine into subgraph plat[Platform]. Cross-zone edges keep working - only the declarations moved.
Zone the right: dashboards and ML features into subgraph cons[Consumers]. Three zones, story readable from the containers alone.
Add the palette at the bottom: three classDef lines - stream, batch, storage.
Apply with ::: at the point of declaration: ev[App events]:::stream, crm[CRM export]:::batch, and storage on both cylinders of truth - warehouse and quarantine.
Read it back as a stranger would: can you tell what streams, what batches, and where data rests, without reading a label? That is the pass condition.
Rescue a messy diagram ★ 6 min · build your own
Here is a real-shaped mess: 15 nodes, zero structure, the diagram you inherit on day one of a new job. Your mission - group it into zones and restyle it using nothing but today's tools. No nodes may be harmed.
flowchart TD a[Web app] --> b[Event API] m[Mobile app] --> b b --> c[Kafka] c --> d[Stream job] e[CRM] --> f[Nightly export] f --> g[S3 landing] g --> h[Batch loader] d --> i[(Staging)] h --> i i --> j[dbt models] j --> k[(Marts)] k --> l[BI tool] k --> n[Feature store] n --> o[Model training]
Diagnose before cutting: read the node list and name the natural zones out loud. (One good answer: Producers, Streaming path, Batch path, Warehouse, Serving.)
Wrap each group in a titled subgraph. Declarations move inside the blocks; the edge lines stay where they are and keep working.
Flip TD to LR and compare. With zones in place, one direction will clearly read better - keep the winner.
Add a palette: stream and batch classes on the two paths, storage on the cylinders. Define the classDefs at the bottom, as always.
Count the wire crossings before and after. The rescue recipe - zones, direction, palette - typically cuts them to near zero without touching a single edge definition.
This rescue is a genuinely great first-week move on a new team. Take the scariest diagram in the wiki, zone and restyle it, open a small PR. You learn the architecture, and the team gets a readable map - everyone wins.
Make Nova clickable ★ 4 min · follow along
Last layer: links. Each Nova platform node gets a click-through to its (fictional) repo README - turning the overview diagram into a navigation hub for the whole platform.
flowchart LR
ing[Ingestion] --> qa{Quality checks} --> dw[(Warehouse)]
click ing "https://github.com/nova-platform/ingestion#readme" "Ingestion README"
click qa "https://github.com/nova-platform/quality-checks#readme" "Quality checks README"
click dw "https://github.com/nova-platform/warehouse#readme" "Warehouse README"
Take your Demo 1 source and add one click line per platform node, pointing at the repo README that owns it. Tooltip text in the second quotes.
Test in mermaid.live: hover shows the tooltip, click follows the link. Add _blank after the tooltip if you want a new tab.
Remember the deployment rule: clicks survive on mermaid.live, self-hosted pages, and most docs sites - but GitHub strips them. Keep a plain link list under the fence for GitHub readers.
Try it yourself - this week ◐ 30-45 min total
- Apply the rescue recipe to the messy diagram you brought from Session 1: zones, direction check, palette. Post before/after in your team channel and count the reactions.
- Write a three-class palette for YOUR domain (maybe
realtime/batch/external?) and reuse it in two different diagrams. Consistency across diagrams is the compounding win. - Rebuild Nova v2 from memory - zones and classes included - then diff against Demo 1. Whatever you forgot is your review list.
- Try three shapes from the v11
@{ shape: ... }catalog you have never used, and decide if any earn a place in your house conventions. - Bring to Session 3: one process you can describe in prose but have never diagrammed - a retry flow, an approval chain, an incident timeline. Sequence diagrams are next, and prose is their raw material.
Official docs covered
This session teaches the working content of these sections of the flowchart page at mermaid.js.org (mermaid 11.x). Left with the official docs: the full 30+ shape catalog table and the animated-edge extras.
Three questions before you go 🎯 ◐ 90 seconds
1 · Which line opens a subgraph you can later draw edges to by ID?
ID first, display title in brackets, close with end. The ID (plat) is what edges and class statements reference later.
2 · Six nodes need the same look. classDef or style?
classDef is CSS-thinking: define a meaning once, apply everywhere, change in one place. Six style lines is six future edits.
3 · Your flowchart hit 30 nodes and answers three different questions. Best move?
Styling cannot save a diagram that answers too many questions. One diagram, one question - link the family together.