Mermaid Diagram Guidelines (MDG v1.0)
This skill enables creation of MDG-compliant Mermaid diagrams with consistent styling, cognitive optimization, and support for both visual presentation and knowledge-graph extraction.
Quick Start
Emergency Rules (Always Apply)
- Use only
<br>to indicate line breaks inside nodes, but only insert line breaks when absolutely needed for some known reason - CRITICAL for flowchart: Always include
color:#000000in every classDef — Without this, text is invisible in dark-mode rendering environments. (Flowchart only — classDiagram does NOT supportclassDef; usethemeVariablesinstead.) - CRITICAL for flowchart: Apply styles to INDIVIDUAL NODES, not subgraphs — Subgraph styling does not propagate to node text. Use
class NodeA,NodeB myStylefor nodes. Usestyle SubgraphID ...for subgraph containers. - CRITICAL: Subgraph identifiers MUST NOT contain spaces — use CamelCase or underscores
- CRITICAL: Subgraph titles need darker styling — Use
styledirective to ensure subgraph labels are visible against colored backgrounds - CRITICAL: Every diagram MUST have a title — Use the appropriate title mechanism for each diagram type (see Title Requirements below)
- First non-blank line must be an MDG header tag (e.g.,
%%mdg:DGM:1.0%%) - If metadata unknown, ask user once, then fill in available information
- Mobile limitation: Mermaid artifacts render in artifact panel on desktop but may not work on mobile
- CRITICAL for flowchart: Default to targeting subgraphs, not leaf nodes, when crossing container boundaries. Targeting leaf nodes inside a container that uses
directionfor arrangement (a "directional container") often overrides the container's internal layout. If semantic accuracy requires targeting a specific interior node, do so — but expect possible layout instability and test carefully. (See Principle 3 Critical Warning below) Corollary: When all child subgraphs inside a container are targets, use a single edge to the parent container (e.g.,Present --> Futures) instead of separate edges to each child (e.g.,Present --> PathA,Present --> PathB,Present --> PathC). Multiple edges into a container's children break the container's internal arrangement. - CRITICAL for flowchart: Every subgraph MUST have an explicit
directionthat alternates with its children. The cross-grain chain must be unbroken at every nesting level. If the flowchart is TB, a title-wrapper subgraph must bedirection TB, and its child subgraphs must bedirection LR. Omittingdirectionon any intermediate subgraph breaks the layout. (See Principle 1.) - CRITICAL for flowchart: Max 4 nodes in any single horizontal chain. A
direction LRsubgraph with 5+ nodes in a single-->chain produces an extremely wide, thin diagram that won't fit any medium. Break long chains into multiple stacked rows (each row a subgraph withdirection LRcontaining ≤ 4 nodes). Similarly, hub-and-spoke layouts (flowchart LR) with >3 spokes must wrap spokes into row containers. - CRITICAL: Aspect ratio must stay between 2:1 and 1:2. If a diagram would be wider than 2× its height (or taller than 2× its width), restructure before publishing. Common fixes: break horizontal chains into rows, wrap hub spokes into row containers, or split into multiple diagrams.
- CRITICAL for flowchart: Sibling subgraphs MUST have
~~~invisible links between them. When a parent subgraph contains two or more child subgraphs at the same level, always add~~~links between them (e.g.,ChildA ~~~ ChildB ~~~ ChildC). Do NOT rely ondirection, external edges, or declaration order alone — renderers handle these inconsistently. The~~~link is the only reliable way to enforce sibling subgraph arrangement. Exception for leaf nodes: Do NOT add~~~between leaf nodes inside a subgraph when the parent subgraphs are already rank-constrained by-->edges — in that case,direction LRis sufficient and adding~~~can break the layout by forcing an overly wide horizontal row. - CRITICAL: Do not create a Legend Object or Subgraph.
Quick Reference: Flowchart Layout Techniques
| Layout Goal | Technique | Key Constraint |
|---|---|---|
| Floating header/title | T1 | style wrapper transparent; use ~~~ not --> to connect |
| Side-by-side (from common source) | T2 | External node → child subgraphs (not leaf nodes) |
| Stacked vertically | T3 | Sequential edges between child subgraphs |
| Side-by-side (aspect ratio) | T4 | direction TB on children makes them narrow |
| Grid (N×M) | T5 | direction LR rows + direction TB children + ~~~ ordering |
| Hub-and-spoke | T6 | Central node at top level; subgraph-level edges to spokes |
| NEVER | — | External edges to leaf nodes inside directional containers |
Flowchart Layout Decision Tree
- Simple stepwise pipeline → subgraph-to-subgraph sequential flow (T3)
- Peer paths from a common source → external common-source edges to child subgraphs (T2 + T4)
- Peer subgraphs side by side (no common source) →
direction TBon children for aspect ratio; combine with~~~if ordering matters (T4) - Grid or quadrant (2×2, N×M) → row containers with
direction LR+ childdirection TB+~~~ordering (T5) - Hub-and-spoke / radial → central node with subgraph-level edges to spoke containers (T6)
- Asymmetric composite → stacked row containers; each row may have 1, 2, or 3 children (T5 variant)
- Would your design require a cross-boundary edge to a leaf node inside a directional container? → redesign to target the containing subgraph instead, or split diagrams
Rule Priority (when rules conflict)
- Syntax validity (character safety, no spaces in IDs)
- Emergency Rules 1–10
- Text visibility / accessibility (
color:#000000, contrast) - Layout stability (Principles 1–3)
- Aesthetic preferences (palettes, legends, shapes)
Title Requirements
Every diagram must include a title. The mechanism varies by diagram type:
| Diagram Type | Title Mechanism | Example |
|---|---|---|
flowchart |
Top-level subgraph with title text, or a prominent styled node at top | subgraph Title["My Diagram Title"] wrapping all content |
sequenceDiagram |
title directive |
title API Authentication Flow |
erDiagram |
title directive |
title Customer Order Model |
stateDiagram-v2 |
title directive |
title Order Lifecycle |
classDiagram |
title directive |
title Domain Model |
gantt |
title directive |
title Project Timeline |
mindmap |
Root node serves as title | Root node text is the title |
pie |
title directive |
title Budget Allocation |
For flowcharts, the recommended approach is a top-level container subgraph whose label serves as the diagram title, styled with prominent font weight and color.
Operating Modes
Diagram Mode (default): For human visual consumption
- Prioritize: color palettes, node limits, legends, aesthetics
- Target: presentations, documentation, slides
RAG Mode: For knowledge-graph extraction
- Prioritize: one-fact-per-line, stable IDs, semantic structure
- Use header:
%%mdg:RAG:1.0%% - Target: semantic web, triples, RAG pipelines
Detection:
- Header tag
%%mdg:RAG:*%%→ RAG Mode - Keywords describing output type (not subject matter): "knowledge graph", "triples", "semantic extraction", "ontology" → RAG Mode
- "Draw a flowchart of my RAG pipeline" → Diagram Mode (the word "RAG" describes the subject, not the output)
- If uncertain → ask the user: "Should this output be a visual diagram or machine-readable triples?"
- Otherwise → Diagram Mode
Diagram Type Selection
| Type | When to Use |
|---|---|
flowchart |
Process flows, system architecture, concept maps |
sequenceDiagram |
API calls, message passing, protocol handshakes |
erDiagram |
Database schemas, entity relationships, data models |
stateDiagram-v2 |
State machines, lifecycle models, status workflows |
classDiagram |
Class models, entity relationships, UML |
gantt |
Timelines, project schedules, milestones |
mindmap |
Brainstorming, topic hierarchies, knowledge maps |
pie |
Proportional data, budget breakdowns, distributions |
If uncertain about layout technique, default to Technique 3 (stacked pipeline).
Standard Guidelines
| Topic | Guideline |
|---|---|
| Character safety | IDs = A-Z a-z 0-9 _ - only |
| Direction | TB for docs; LR for slides/mobile |
| Legend | Required if > 3 shape types OR > 2 color families |
| Node count | Aim ≤ 60 nodes & ≤ 100 edges for flowcharts (split if needed) |
| Horizontal chain limit | Max 4 nodes in a single direction LR chain. If more, break into multiple rows (stacked subgraphs with direction LR each containing ≤ 4 nodes) or switch the parent to flowchart LR with direction TB children. |
| Aspect ratio | Target 4:3 to 3:4. Never exceed 2:1 (wide) or 1:2 (tall). If a draft exceeds this, restructure: break long chains into rows, wrap spokes into row containers, or split into separate diagrams. |
| Accessibility | Font ≥ 14px mobile / 16px desktop; contrast ≥ 4.5:1 |
| Titles | Every diagram must have a title — no exceptions |
| Nesting | Limit subgraph nesting to 2 levels max (see flowchart sections below) |
| Layout engine | Default: dagre (always available). ELK: optional, better for complex diagrams but NOT available in claude.ai artifacts (see ELK section below) |
Pre-Publish Checklist
Before delivering, verify:
- Header tag present and correct mode
- Metadata complete
- Title present — every diagram has a title via the appropriate mechanism for its type
- No spaces in subgraph identifiers
- Font size initialized
- For flowchart: Every classDef includes
color:#000000 - For flowchart: Styles applied to individual nodes (via
class), not subgraphs - For flowchart with subgraphs: Subgraph titles styled with dark text (
color:#1a1a1aorcolor:#000000) viastyledirective - For flowchart with subgraphs: Cross-grain direction — child subgraph
directionis perpendicular to parent flow (or deliberately overridden with justification) - For flowchart with subgraphs: Subgraph arrangement — verify against Quick Reference table: pipeline (T2/T3), grid (T5), hub-and-spoke (T6). NO cross-boundary edges targeting leaf nodes inside directional containers (Emergency Rule 10).
- For flowchart with subgraphs: Subgraph-level connections — cross-subgraph arrows target subgraphs, not interior nodes (unless a specific interior node must be identified)
- Aspect ratio check: Ratio stays between 2:1 and 1:2. No
direction LRchain exceeds 4 nodes. Hub-and-spoke has ≤ 3 unwrapped spokes. - For classDiagram: Using
theme: 'base'with proper themeVariables - For classDiagram:
lineColorset to light value (e.g.,#AAAAAA) - For sequenceDiagram:
titledirective present; participant count ≤ 8; using+/-activation shorthand - For erDiagram: All relationships labeled; PK/FK annotated; singular entity names
- For stateDiagram-v2: Start and end states present; all transitions labeled
- For gantt:
titledirective present; dependencies used over hardcoded dates - If using advanced layout: Invisible container/anchor classes defined
- If using advanced layout: All padding/anchor nodes have
invisibleclass applied - Legend if > 3 shapes or > 2 color families
- Node count ≤ 60 (or deliberately exceeded with justification)
Change Log
Current standard: MDG v1.0
| Version | Date | Summary |
|---|---|---|
| 1.0 | 2026-03-07 | Public release. Single-file consolidation. Four rounds of multi-LLM review (GPT, Gemini, Grok, Sonnet, Haiku). No breaking changes from v0.49. |
| 0.49 | 2026-03-07 | Round 3 micro-fixes: RAG triple example updated to convention-compliant IDs. Frontmatter description corrected. Technique 6 duplicate sentence removed. Safe Fallback title-wrapper clarification added. |
| 0.48 | 2026-03-07 | Round 2 review (16 changes): troubleshooting section, safe fallback default, routing enforcement, asymmetric composite example, ELK tightening, hub-spoke threshold, Quick Rule. Plus 9 targeted fixes. |
| 0.47 | 2026-03-07 | Structural reorganization. Non-flowchart depth (sequence, ERD, state, Gantt, mindmap, pie, class). ELK layout engine reference. |
| 0.46 | 2026-03-06 | Principle 3 comprehensive rewrite: 5 techniques for subgraph arrangement including Grid Layout. |
| 0.45 | 2026-02-27 | Page-Fit Layout Strategy. New diagram types. |
| 0.44 | 2025-12-04 | Advanced Layout Techniques. Invisible elements toolkit. |
| 0.43 | 2025-11-28 | classDiagram guidance. color:#000000 rule. |
| 0.40 | 2025-09-02 | Initial structured version. |
| 0.39 | 2025-05-24 | Revision. |
| 0.38d3 | 2025-05-04 | Draft 3. |
| 0.38d2 | 2025-04-27 | Draft 2. |
| 0.38d1 | 2025-04-27 | First draft. |
Diagram Type Reference
flowchart Diagrams
flowchart Boilerplate
%%mdg:DGM:1.0%%
%%{init: {'theme': 'default', 'themeVariables': { 'fontSize': '20px' }}}%%
%% Date: YYYY-MM-DD
%% Title: Diagram Title
%% Project: Project Name
%% MDG Version: 1.0
%% LLM: Claude [version]
%% Human Author: [Name]
%% Location: [Location]
flowchart TB
subgraph DiagramTitle["Diagram Title Here"]
A[Node A] --> B[Node B]
end
classDef primary fill:#E0E8FF,stroke:#3050C8,stroke-width:2px,color:#000000
class A,B primary
style DiagramTitle fill:#F5F8FF,stroke:#3050C8,stroke-width:2px,color:#1a1a1a,font-weight:bold
Critical Syntax Rules
Subgraph Declaration (MOST COMMON ERROR)
❌ INCORRECT (causes syntax errors):
subgraph Current State["Current State"]
subgraph Data Layer["Data Management"]
✅ CORRECT:
subgraph CurrentState["Current State"]
subgraph DataLayer["Data Management"]
Rule: NO SPACES between identifier and brackets. Use CamelCase, underscores, or single words.
Node Styling for flowchart (CRITICAL FOR TEXT VISIBILITY)
❌ INCORRECT — Applying styles to subgraphs (text inside nodes will be invisible):
subgraph MyGroup["My Group"]
NodeA[Node A]
NodeB[Node B]
end
classDef myStyle fill:#E0E8FF,stroke:#3050C8,stroke-width:2px,color:#000000
class MyGroup myStyle
✅ CORRECT — Apply styles to individual nodes:
subgraph MyGroup["My Group"]
NodeA[Node A]
NodeB[Node B]
end
classDef myStyle fill:#E0E8FF,stroke:#3050C8,stroke-width:2px,color:#000000
class NodeA,NodeB myStyle
Rule: Always apply class statements to individual node IDs, not subgraph IDs.
Subgraph Title Styling (CRITICAL FOR COLORED DIAGRAMS)
When using colored backgrounds or pastel fills, subgraph titles can become hard to read. Always style subgraph titles with darker, high-contrast colors.
✅ CORRECT — Style subgraph titles for visibility:
flowchart TB
subgraph Frontend["Frontend Layer"]
UI[User Interface]
Router[Router]
end
subgraph Backend["Backend Services"]
API[API Gateway]
DB[(Database)]
end
UI --> API
Router --> API
API --> DB
classDef primary fill:#E0E8FF,stroke:#3050C8,stroke-width:2px,color:#000000
classDef secondary fill:#E0FFE8,stroke:#30C850,stroke-width:2px,color:#000000
class UI,Router primary
class API,DB secondary
style Frontend fill:#F5F8FF,stroke:#3050C8,stroke-width:2px,color:#1a1a1a,font-weight:bold
style Backend fill:#F5FFF8,stroke:#30C850,stroke-width:2px,color:#1a1a1a,font-weight:bold
Subgraph Title Styling Guidelines:
- Use
style SubgraphIDdirective (notclass) for subgraph containers - Set
color:#1a1a1aorcolor:#000000for dark, readable title text - Add
font-weight:boldto improve title visibility - Use lighter fill colors for subgraph backgrounds than for nodes (e.g.,
#F5F8FFinstead of#E0E8FF) - Ensure stroke color matches or complements the node color family
Page-Fit Layout Strategy: Direction and Connection Patterns
These two principles control diagram aspect ratio and prevent oversized diagrams. Apply them by default to every flowchart with subgraphs.
Principle 1: Cross-Grain Direction
Rule: Every subgraph MUST have an explicit direction that is the opposite of its children's direction. The alternating cross-grain pattern must be unbroken at every nesting level — no gaps allowed.
| Parent Direction | Child Subgraph Direction | Effect |
|---|---|---|
flowchart TB or flowchart TD |
direction LR inside subgraphs |
Each step is a horizontal band; steps stack vertically |
flowchart LR |
direction TB inside subgraphs |
Each step is a vertical column; steps flow horizontally |
Critical: The chain must be explicit at every level. If a title-wrapper subgraph sits between the flowchart declaration and the leaf subgraphs, it MUST have an explicit direction that continues the alternating pattern. Omitting direction on any intermediate subgraph breaks the chain and causes unpredictable layout.
flowchart TB
subgraph Title["My Diagram"]
direction TB ← explicit, same as flowchart (contains LR children)
subgraph Stage1["Step 1"]
direction LR ← opposite of parent (TB)
A["Node A"] --> B["Node B"]
end
subgraph Stage2["Step 2"]
direction LR ← opposite of parent (TB)
C["Node C"] --> D["Node D"]
end
Stage1 --> Stage2
end
Rationale: If parent and child share the same axis, expansion compounds — a 5-step pipeline with 3 nodes per step produces 15 vertical units of height. Cross-grain direction makes each step expand across the flow, not along it. If any subgraph in the chain omits direction, renderers may fall back to defaults that break the intended layout.
Critical: Limit horizontal chain length. Even with correct cross-grain direction, a single direction LR subgraph with 5+ nodes in a --> chain produces an extremely wide, thin layout that exceeds the 2:1 aspect ratio limit. Max 4 nodes per horizontal chain. For longer sequences, break into multiple stacked subgraphs:
❌ Too wide (6 nodes in one LR chain):
subgraph Pipeline["Event Pipeline"]
direction LR
A --> B --> C --> D --> E --> F
end
✅ Wrapped into rows (max 3 per row):
subgraph Pipeline["Event Pipeline"]
direction TB
subgraph Row1["Ingestion"]
direction LR
A --> B --> C
end
subgraph Row2["Processing"]
direction LR
D --> E --> F
end
Row1 --> Row2
end
When to Override (same-grain is acceptable):
- The subgraph contains a single node (direction is irrelevant)
- The subgraph's content is genuinely sequential along the same axis as the parent (rare)
- You're deliberately creating a tall/narrow or wide/short layout for a specific medium
- Explicit edges between child subgraphs (
==>) establish rank ordering — in this case, the edges control arrangement, not the parentdirection(see Combined Example: Multi-Phase Timeline in Advanced Layout)
❌ Same-grain (tall, won't fit on page):
flowchart TB
subgraph Step1["Step 1"]
direction TB
A["Input"] --> B["Process"] --> C["Output"]
end
subgraph Step2["Step 2"]
direction TB
D["Input"] --> E["Process"] --> F["Output"]
end
Step1 --> Step2
✅ Cross-grain (compact, fits on page):
flowchart TB
subgraph Step1["Step 1"]
direction LR
A["Input"] --> B["Process"] --> C["Output"]
end
subgraph Step2["Step 2"]
direction LR
D["Input"] --> E["Process"] --> F["Output"]
end
Step1 --> Step2
Nested Subgraphs — alternate direction at each level:
flowchart TB ← Level 0: vertical
subgraph Phase1 ← Level 1: horizontal
direction LR
subgraph Detail1 ← Level 2: vertical (if needed)
direction TB
end
end
Generally avoid more than 2 levels of nesting. If you need 3+, split into multiple diagrams.
Principle 2: Subgraph-Level Connections
Rule: Arrows between subgraphs should connect to the subgraph itself, not to nodes inside the subgraph, unless there is a specific reason to target an interior node.
Rationale: When you write NodeA_Inside_Sub1 --> NodeB_Inside_Sub2, the layout engine must route an arrow from a specific interior point in Subgraph 1 to a specific interior point in Subgraph 2. This forces spacing expansion, produces long diagonal arrows, and prevents the engine from optimizing compactness.
When you write Sub1 --> Sub2, the engine attaches the arrow to the subgraph border at the optimal attachment point. The arrow is short, clean, and the engine retains layout freedom.
Decision Heuristic:
| Situation | Connection Level | Example |
|---|---|---|
| Sequential steps in a pipeline | Subgraph → Subgraph | Step1 --> Step2 |
| Feedback loops between steps | Subgraph → Subgraph | Step5 --> Step4 |
| Parallel paths from a fork | Node → Subgraph | FORK --> PathA |
| Data flow between specific ports | Node → Node (justified) | API_Output --> DB_Input |
| Error handling from a specific step | Node → Node (justified) | Validator --> ErrorHandler |
The Test: Before writing a cross-boundary node-to-node connection, ask: "Does the reader need to know which specific interior element is the source/target, or just that these two steps are connected?"
- If they need to know → node-to-node (justified)
- If they just need the flow → subgraph-to-subgraph (default)
❌ Node-to-node across subgraphs (sprawling):
flowchart TB
subgraph Step1["Processing"]
direction LR
A["Input"] --> B["Transform"] --> C["Output"]
end
subgraph Step2["Validation"]
direction LR
D["Receive"] --> E["Validate"] --> F["Result"]
end
C --> D
F -.-> A
✅ Subgraph-to-subgraph (compact):
flowchart TB
subgraph Step1["Processing"]
direction LR
A["Input"] --> B["Transform"] --> C["Output"]
end
subgraph Step2["Validation"]
direction LR
D["Receive"] --> E["Validate"] --> F["Result"]
end
Step1 --> Step2
Step2 -.-> Step1
Principle 3: Subgraph Arrangement (The Single Biggest Layout Issue)
Arranging subgraphs — side-by-side or stacked — is the most common layout problem in Mermaid flowcharts.
dagre (from "directed graph rendering") is the JavaScript layout engine underneath Mermaid. Mermaid handles syntax and styling; dagre makes all spatial decisions — where nodes sit, how edges route, and how subgraphs are arranged. Every layout behavior described in this section is a dagre decision, not a Mermaid decision.
Renderer scope: These layout rules are empirical patterns verified in the claude.ai artifact panel running Mermaid 10.x with dagre layout. Other renderers (VS Code, GitLab, GitHub, Mermaid Live Editor) may behave differently. Treat these as high-value defaults, not mathematical guarantees. When a layout fails, simplify edges, reduce asymmetry, and prefer the fallback patterns listed below.
The direction directive on a parent subgraph to control children's arrangement is not dependable as a sole control mechanism (dagre frequently deprioritizes it when it conflicts with global rank assignments). However, direction on child subgraphs to control their internal flow is respected and critically important — it shapes each child's aspect ratio, which indirectly influences how dagre arranges peers. For simple pipeline layouts, the most reliable observed pattern is container subgraphs with external rank-forcing edges, combined with direction on children to control internal flow. For grid layouts (2×2 or N×M), parent direction combined with child direction and ~~~ ordering links is the most reliable currently-known pattern (see Technique 5).
The Core Problem
When you set direction LR inside a parent subgraph to arrange its children side by side, dagre often deprioritizes it and stacks children vertically. The direction directive on a parent is a weak hint that dagre may disregard when it conflicts with edge-derived rank assignments.
However, direction on a child subgraph controls that child's internal flow — and this IS respected. This matters because a child's internal flow determines its aspect ratio (tall/narrow vs short/wide), and aspect ratio influences how dagre arranges peer subgraphs.
The key distinction:
direction applied to |
Purpose | Reliability |
|---|---|---|
| Parent subgraph (alone) | Arrange children side-by-side or stacked | ⚠️ Weak hint — not dependable as sole arrangement mechanism |
Parent subgraph + child direction + ~~~ ordering |
Grid layout arrangement (2×2, N×M) | ✅ Most reliable observed pattern (see Technique 5) |
| Child subgraph | Control internal flow within that child | ✅ Reliable — shapes aspect ratio, indirectly influences peer arrangement |
⚠️ Critical Warning: Cross-Boundary Edges and Interior Targets
Quick Rule: If you write External --> Something, that "Something" must be a subgraph ID, not a node inside it.
Read this before applying any technique. This is the most common cause of layout failure.
When edges from outside a container target leaf nodes inside that container, dagre assigns those leaf nodes their own rank based on the external edge — overriding the container's internal direction arrangement. This pulls siblings out of side-by-side positioning and stacks them vertically.
The key distinction: subgraph targets vs leaf-node targets.
| Target type | Example | Safe? |
|---|---|---|
| Child subgraph inside container | External --> PathA (PathA is a subgraph) |
✅ Safe — dagre treats subgraph-level rank assignment differently |
| Leaf node inside directional container | External --> NodeX (NodeX is a node inside a subgraph with direction set) |
❌ Unsafe — external rank overrides internal arrangement |
| Container subgraph itself | StepA --> Container |
✅ Safe — subgraph-to-subgraph connection |
❌ Breaks BottomRow's side-by-side arrangement:
TopRow --> BottomRow
TopRow --> ChildC %% ChildC is a leaf node inside BottomRow
TopRow --> ChildD %% ChildD is a leaf node inside BottomRow
✅ Preserves arrangement:
TopRow --> BottomRow %% subgraph-to-subgraph only
Technique 2 is safe because its external edges target child subgraphs (e.g., Reality --> PathA), not leaf nodes. The failure mode occurs when you target leaf nodes inside a subgraph that has its own direction set.
Rule: When crossing container boundaries, target subgraphs, not their contents.
The Reliable Solution: Container + External Edges
The pattern that works consistently for both side-by-side and stacked arrangements:
- Use a visible container subgraph to group related children and provide semantic context (e.g.,
subgraph x["INTERPRETATION PATHWAYS"]) - Place rank-forcing edges from a node OUTSIDE the container into the children inside it — this is what actually controls arrangement
- Use a transparent wrapper subgraph for elements that should float above or outside the container (titles, headers) while still participating in layout
Why it works: Dagre determines rank from edges. When an external node connects to multiple children inside a container, dagre assigns them equal rank. In a flowchart TB, equal-rank subgraphs are placed side by side. In a flowchart LR, they stack vertically. The container provides the visual border; the external edges provide the layout control.
Technique 1: Transparent Wrapper for Floating Elements
Use subgraph y[" "] with transparent styling to hold title nodes or header elements outside the main visual container:
subgraph y[" "]
direction TB
Title["Diagram Title"]
HeaderNode["Key context"]
Title ~~~ HeaderNode
end
style y fill:transparent,stroke:transparent,stroke-width:0px
This lets elements float cleanly above a container without being boxed inside it.
Technique 2: Side-by-Side via Container + External Edges
Place peer subgraphs inside a visible container. Connect to them from an external node:
flowchart TB
%% Floating header (transparent wrapper)
subgraph y[" "]
Title["Figure Title"]
Reality["Starting Condition"]
Title ~~~ Reality
end
style y fill:transparent,stroke:transparent,stroke-width:0px
%% Visible container with peer subgraphs
subgraph x["PATHWAYS"]
subgraph PathA["Path A"]
A1["Step 1"] --> A2["Step 2"] --> A3["Step 3"]
end
subgraph PathB["Path B"]
B1["Step 1"] --> B2["Step 2"] --> B3["Step 3"]
end
subgraph PathC["Path C"]
C1["Step 1"] --> C2["Step 2"] --> C3["Step 3"]
end
end
%% External edges force equal rank → side-by-side
Reality --> PathA
Reality --> PathB
Reality --> PathC
Key: The edges from Reality (outside x) into PathA, PathB, PathC (inside x) usually encourage equal-rank placement. In flowchart TB, this often yields side-by-side arrangement when child aspect ratios are compatible. Note: these external edges target child subgraphs — this is safe per the Critical Warning above. If peers still stack, add direction TB inside each child to make them narrower (Technique 4).
Technique 3: Stacked Arrangement via Container + Sequential Edges
The same container pattern works for stacked (vertical) arrangements. Instead of connecting all children to the same external node, connect them in sequence:
flowchart TB
subgraph y[" "]
Title["Figure Title"]
end
style y fill:transparent,stroke:transparent,stroke-width:0px
subgraph x["EVIDENCE LAYERS"]
subgraph Layer1["Mechanism Evidence"]
L1A["Study A"] ~~~ L1B["Study B"]
end
subgraph Layer2["Domain Evidence"]
L2A["Study C"] ~~~ L2B["Study D"]
end
subgraph Layer3["Outcome Evidence"]
L3A["Study E"] ~~~ L3B["Study F"]
end
end
Title --> x
Layer1 --> Layer2
Layer2 --> Layer3
Key: Sequential edges (Layer1 --> Layer2 --> Layer3) assign different ranks, forcing vertical stacking. Within each layer, ~~~ links keep peer nodes side by side.
Technique 4: direction on Children for Aspect Ratio Control
Use direction TB inside each child subgraph to make its internal content flow vertically. This makes each child taller and narrower, which causes dagre to place peer subgraphs side by side (because they fit horizontally). Without direction TB, node chains default to flowing horizontally, making each child wider, which triggers vertical stacking.
This is the single most impactful layout technique for side-by-side arrangements.
✅ With direction TB on children — peers render side by side:
flowchart TB
subgraph x["PATHWAYS"]
subgraph PathA["Path A"]
direction TB
A1["Step 1"] --> A2["Step 2"] --> A3["Step 3"]
end
subgraph PathB["Path B"]
direction TB
B1["Step 1"] --> B2["Step 2"] --> B3["Step 3"]
end
subgraph PathC["Path C"]
direction TB
C1["Step 1"] --> C2["Step 2"] --> C3["Step 3"]
end
end
ExternalNode --> PathA
ExternalNode --> PathB
ExternalNode --> PathC
Why it works: direction TB forces each path's nodes into a vertical column. Three narrow vertical columns fit side by side. Without direction TB, the nodes chain horizontally, creating wide subgraphs that dagre stacks vertically to fit.
Combine with external edges for stronger control: direction TB on children shapes aspect ratio. External edges from a common node encourage equal rank. Together, they produce the most consistent side-by-side arrangement observed in testing.
Technique 5: Grid Layout (2×2 or N×M)
For arrangements where multiple rows each contain side-by-side subgraphs (e.g., a 2×2 quadrant diagram), the container + external edges pattern from Technique 2 does NOT work — because external edges targeting interior nodes of a container break the container's internal arrangement (see Critical Warning above). Instead, use parent direction LR on each row container, child direction TB for aspect ratio, and ~~~ for left-right ordering.
The pattern:
flowchart TB
%% Floating title (transparent wrapper)
subgraph y[" "]
TitleNode["Diagram Title"]
end
style y fill:transparent,stroke:transparent,stroke-width:0px
%% Row 1: children side by side
subgraph Row1["Row 1 Label"]
direction LR
subgraph ChildA["Child A"]
direction TB
A1["Item 1"] --> A2["Item 2"]
end
subgraph ChildB["Child B"]
direction TB
B1["Item 1"] --> B2["Item 2"]
end
ChildA ~~~ ChildB
end
%% Row 2: children side by side
subgraph Row2["Row 2 Label"]
direction LR
subgraph ChildC["Child C"]
direction TB
C1["Item 1"] --> C2["Item 2"]
end
subgraph ChildD["Child D"]
direction TB
D1["Item 1"] --> D2["Item 2"]
end
ChildC ~~~ ChildD
end
%% Connect title to first row invisibly (no visible arrow)
TitleNode ~~~ Row1
%% Stack rows — subgraph-to-subgraph ONLY
Row1 --> Row2
Why it works — three mechanisms combine:
direction LRon each row container — hints dagre to arrange children horizontally within the rowdirection TBon each child — makes children tall/narrow, fitting side by sideChildA ~~~ ChildB— tends to preserve left-to-right ordering consistent with declaration order (verify visually — the ordering heuristic is non-deterministic)
Critical rules for grid layouts:
- Connect rows to each other with subgraph-to-subgraph edges only (
Row1 --> Row2). Never target leaf nodes across row boundaries. - Use
~~~(not-->) from title/header nodes to avoid unwanted visible arrows. - The
~~~link between siblings inside a row tends to preserve declaration-order positioning — declare the left sibling first in the~~~expression. - If exact ordering matters, combine
~~~with row containers and keep sibling structure symmetric.
Technique 6: Hub-and-Spoke (Radial Approximation)
For diagrams with a central concept and N satellite nodes (e.g., "Core API with 5 services," "Central thesis with supporting arguments"), do NOT use grid or pipeline techniques — they force rigid structures that look wrong for radial concepts.
The pattern: Place the central node at top level. Connect it with subgraph-level edges to spoke containers. Do not wrap spokes in a parent container unless semantic grouping requires it.
flowchart TB
Hub((Central Concept))
subgraph SpokeA["Service A"]
direction TB
A1["Endpoint 1"] --> A2["Endpoint 2"]
end
subgraph SpokeB["Service B"]
direction TB
B1["Endpoint 1"] --> B2["Endpoint 2"]
end
subgraph SpokeC["Service C"]
direction TB
C1["Endpoint 1"] --> C2["Endpoint 2"]
end
Hub --> SpokeA
Hub --> SpokeB
Hub --> SpokeC
Key: No container wrapping the spokes. The hub's edges to each spoke usually encourage equal rank placement. Use direction TB inside spokes to keep them narrow.
Scale threshold: For >3 spokes, the layout becomes too wide for most media. Transition to a hybrid: place spokes in row containers (Technique 5 variant) with 2-3 spokes per row, stacked vertically. Or split into two hub-and-spoke diagrams (primary hub + secondary hub). A flowchart LR with 4+ spokes will exceed a 2:1 aspect ratio and fail the aspect-ratio guideline.
Row-wrapped hub-and-spoke (5 spokes):
flowchart LR
Hub(("Central<br>Concept"))
subgraph Row1["Primary"]
direction TB
subgraph SpokeA["Service A"]
direction TB
A1["Step 1"] --> A2["Step 2"]
end
subgraph SpokeB["Service B"]
direction TB
B1["Step 1"] --> B2["Step 2"]
end
subgraph SpokeC["Service C"]
direction TB
C1["Step 1"] --> C2["Step 2"]
end
SpokeA ~~~ SpokeB ~~~ SpokeC
end
subgraph Row2["Secondary"]
direction TB
subgraph SpokeD["Service D"]
direction TB
D1["Step 1"] --> D2["Step 2"]
end
subgraph SpokeE["Service E"]
direction TB
E1["Step 1"] --> E2["Step 2"]
end
SpokeD ~~~ SpokeE
end
Hub --> Row1
Hub --> Row2
Row1 ~~~ Row2
Asymmetric composite layouts (e.g., 1 wide row + 2 narrow below) are a variant of Technique 5: build stacked row containers where each row may have a different number of children. Use direction LR on each row, ~~~ inside each row for ordering, and Row1 --> Row2 between rows.
Asymmetric example (1 wide + 2 narrow):
flowchart TB
subgraph Row1["Overview"]
direction LR
Summary["Full-Width Summary Content"]
end
subgraph Row2["Details"]
direction LR
subgraph Left["Left Detail"]
direction TB
L1["Item A"] --> L2["Item B"]
end
subgraph Right["Right Detail"]
direction TB
R1["Item C"] --> R2["Item D"]
end
Left ~~~ Right
end
Row1 --> Row2
Key: Row1 has 1 child (naturally spans full width). Row2 has 2 children with direction TB (narrower, side by side). Subgraph-to-subgraph stacking only.
When to Use Which Technique
| Goal | Technique | Pattern |
|---|---|---|
| Peer subgraphs side by side | Container + external edges from ONE node to ALL child subgraphs + direction TB inside each child |
External --> ChildA, External --> ChildB + direction TB in each child |
| Peer subgraphs stacked vertically | Container + sequential edges between children | Child1 --> Child2 --> Child3 |
| Grid layout (2×2, N×M) | Row containers with direction LR + child direction TB + ~~~ ordering + subgraph-to-subgraph stacking |
direction LR on rows, direction TB on children, ChildA ~~~ ChildB inside rows, Row1 --> Row2 between rows |
| Hub-and-spoke / radial | Central node at top level + subgraph-level edges to spoke containers | Hub --> SpokeA, Hub --> SpokeB + direction TB in spokes; no parent container |
| Asymmetric composite (1 wide + 2 narrow) | Stacked row containers; each row may have different child count | Variant of T5: direction LR on each row, ~~~ inside, Row1 --> Row2 between |
| Mixed (some side by side, some stacked) | Combine both — same-source edges for horizontal peers, sequential edges for vertical ordering | Mix patterns as needed |
| Elements floating outside container | Transparent wrapper subgraph | style y fill:transparent,stroke:transparent,stroke-width:0px |
| Control child internal flow | direction TB or `direct |
…(truncated)