SVG Diagram Skill — Production-Grade Handbook
Read this entire file before drawing any diagram. Then consult references/ for detailed specs and complete examples.
Step 1: Diagram Type Decision
Determine the diagram type first, then choose the technical approach.
| User Intent | Type | Approach |
|---|---|---|
| Message interaction, call chains, sequence | Sequence diagram | Hand-written SVG |
| Step flows, decision branches | Flowchart | Hand-written SVG |
| Component containment, system architecture | Structure diagram | Hand-written SVG |
| Abstract concepts, mechanism explanation | Illustrative diagram | HTML + inline SVG (interactive) |
| Database table structures, ER diagrams | ERD | mermaid.js (see references/erd.md) |
| State transitions, lifecycle | State machine diagram | Hand-written SVG (see references/state-machine.md) |
| Chronological events, milestones | Timeline diagram | Hand-written SVG (see references/timeline.md) |
| Controls needed | Interactive diagram | HTML widget (see references/interactive.md) |
Mandatory split rules (exceed these limits → split into multiple diagrams):
- Sequence participants > 6 → split into "overview + sub-flow"
- Horizontal flow nodes > 5 → switch to vertical or split
- Structure nesting > 3 levels → split into "system-level + component-level"
- Flows with loops → never draw circular, use HTML stepper (see references/interactive.md)
Step 2: Canvas & Coordinate System
viewBox="0 0 680 {H}" ← 680 is a hard constant, never change it
width="100%"
role="img" ← required for accessibility
How to calculate H (execute in order):
- Complete coordinate calculations for all elements first
- Find max_y = largest (y + height) across all elements
- H = max_y + 40
- Never estimate, never "start with 600 and adjust"
Safe zone: x ∈ [40, 640], y ∈ [40, H-40]
SVG root element skeleton:
<svg width="100%" viewBox="0 0 680 {H}" role="img"
xmlns="http://www.w3.org/2000/svg">
<title>Diagram title</title>
<desc>One-line description for screen readers</desc>
<defs>
<style>
<!-- Copy full style block from references/embedded-styles.md -->
<!-- Keep only the c-* classes actually used in this diagram -->
<!-- Keep corresponding dark-mode @media overrides for used c-* classes -->
</style>
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5"
markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</marker>
</defs>
<!-- Content -->
</svg>
Critical: Both xmlns and <style> are mandatory. The style block must include CSS variables (--b, --bg2, --s), text classes (.t/.ts/.th), and only the c-* color classes used in the diagram with their dark-mode overrides. Copy the complete template from references/embedded-styles.md.
context-stroke makes arrowheads auto-inherit line color. Never use any other method to define arrowhead color.
Step 3: Color System
Color Class Reference
| class | Semantic role | 50 Fill | 600 Stroke | 800 Title | 600 Subtitle |
|---|---|---|---|---|---|
c-teal |
Person / user / actor | #E1F5EE | #0F6E56 | #085041 | #0F6E56 |
c-blue |
Data / information / storage | #E6F1FB | #185FA5 | #0C447C | #185FA5 |
c-purple |
Processing / operation / logic | #EEEDFE | #534AB7 | #3C3489 | #534AB7 |
c-amber |
External system / third-party | #FAEEDA | #854F0B | #633806 | #854F0B |
c-coral |
Async / queue / task | #FAECE7 | #993C1D | #712B13 | #993C1D |
c-gray |
Neutral / structure / start-end | #F1EFE8 | #5F5E5A | #444441 | #5F5E5A |
c-green |
Success / completion / positive | #EAF3DE | #3B6D11 | #27500A | #3B6D11 |
c-red |
Error / failure / warning | #FCEBEB | #A32D2D | #791F1F | #A32D2D |
Color Usage Rules
Must follow:
- Same-type nodes use the same color (group by semantic role, not by sequence)
- Title text uses 800 shade, subtitle text uses 600 shade — never identical (loses hierarchy)
c-*only on<g>,<rect>,<polygon>,<circle>, or<ellipse>— never on<path>c-*uses direct-child selectors; nesting<g>tags breaks styling — avoid deep nesting
Dark mode rules (what separates production from toy):
- All text colors must use CSS variables or
c-*class auto-inversion - Never hardcode hex on text (invisible in dark mode)
- Line colors may use hardcoded hex (mid-tone values readable in both modes, e.g.
#1D9E75) - Only exception: physical-scene diagrams (fire, sky, real-world colors) may hardcode everything, but must provide
@media (prefers-color-scheme: dark)variants - Mixing trap: hardcoded background +
c-*foreground = only half-inverts in dark mode. Never mix.
Step 4: Precise Character Widths
Measured widths at 14px Anthropic Sans (used for calculating rect minimum width):
| Text type | Width per char | Example |
|---|---|---|
| English uppercase A-Z | ~10px | AUTH (4 chars) ≈ 40px |
| English lowercase a-z | ~7px | token (5 chars) ≈ 35px |
| Mixed case English | ~8px/char | Auth Service (12 chars) ≈ 96px |
| Digits 0-9 | ~8px | 200 OK (6 chars) ≈ 48px |
| Chinese characters | ~15px/char | 数据流转 (4 chars) ≈ 60px |
| Mixed CJK + English | estimate per segment | POST /login ≈ 88px |
Special chars _/: |
~6px | user_id ≈ 88px |
Rect minimum width formula:
rect_min_width = max(title_width, subtitle_width) + 48
↑ 24px padding on each side
12px subtitle text width = 14px estimate × 0.86
Verification steps (do before every rect):
- Find the longest text string in that rect
- Estimate rendered width by type
- Add 48px for minimum width
- If planned rect width < minimum width, widen the rect or shorten the text
Step 5: Per-Type Specifications
Each type below lists the core rules. For detailed coordinate tables, complete specs, and full examples, read the corresponding reference file before drawing.
Sequence Diagram
Read references/sequence.md before drawing any sequence diagram.
X-coordinate allocation (by participant count):
| Count | Center X positions |
|---|---|
| 2 | 160, 520 |
| 3 | 120, 340, 560 |
| 4 | 90, 270, 450, 610 (box width 90) |
| 5 | 65, 195, 330, 465, 600 (box width 80) |
| 6 | 50, 160, 282, 390, 505, 621 (box width 90) |
Participant box height fixed at 40px, rx=6.
Lifeline:
<line x1="{cx}" y1="60" x2="{cx}" y2="{H-60}"
stroke="var(--b)" stroke-width="0.5" stroke-dasharray="4 3"/>
Message arrows (solid = active call, dashed = return/async):
<!-- Active call -->
<line x1="{from_cx}" y1="{y}" x2="{to_cx}" y2="{y}"
stroke="{color}" stroke-width="1.5" marker-end="url(#arrow)"/>
<!-- Return / response -->
<line x1="{from_cx}" y1="{y}" x2="{to_cx}" y2="{y}"
stroke="#888780" stroke-width="1" stroke-dasharray="5 3"
marker-end="url(#arrow)"/>
Message label placed 8px above the arrow: y="{arrow_y - 8}"
Phase group background:
<rect x="28" y="{group_y}" width="630" height="{group_h}"
rx="4" fill="var(--bg2)" opacity="0.5"/>
<text class="ts" x="36" y="{group_y + 16}" fill="var(--s)">① Phase name</text>
Spacing rules and group height:
- Adjacent arrows: ≥ 32px; between groups: +20px extra
group_h = (n_msgs + 1) × 32 + 16- For pre-computed Y-coordinate lookup tables (single-group and two-group scenarios), see
references/sequence.md
Flowchart
Read references/flowchart.md before drawing any flowchart.
Layout direction — decide before drawing:
- Horizontal (compact): ≤ 4 nodes, linear flow (no branches), short labels (title only, no subtitle). Produces a short, wide diagram — ideal for simple sequential flows.
- Vertical (spacious): > 4 nodes, has decision branches, needs subtitles, or complex routing. Produces a tall diagram — necessary for branching logic.
Standard node sizes:
- Single-line node: height 44px
- Two-line node (title + subtitle): height 56px
- Decision diamond:
<polygon>, width ~120px, height ~60px
Two-line node text positioning:
<text class="th" x="{cx}" y="{rect_y + 19}"
text-anchor="middle" dominant-baseline="central">Title</text>
<text class="ts" x="{cx}" y="{rect_y + 38}"
text-anchor="middle" dominant-baseline="central">Subtitle</text>
Horizontal layout width calculation:
gap = 40 (minimum — 10px exit + 20px visible shaft + 10px entry)
total = sum(node_w) + (n-1) × gap, must satisfy total ≤ 600
node_w = max(title_width + 48, 90) per node — variable width
start_x = (680 - total) / 2
Vertical arrow endpoints (leave 10px gap):
Upper node exit: y1 = rect_y + rect_h + 10
Lower node entry: y2 = rect_y - 10
Backward / return edge (L-shaped):
<path d="M {x1} {y1} L {x_detour} {y1} L {x_detour} {y2} L {x2} {y2}"
fill="none" stroke="#E24B4A" stroke-width="1"
stroke-dasharray="5 3" marker-end="url(#arrow)"/>
Decision diamonds: Exit points are the left/right corners. Downstream boxes center-align to exit corners, NOT to diamond center. Non-overlap constraint: box_right_x ≥ box_left_x + box_w + 20. For full branching layout, arrow routing templates, and common mistakes, see references/flowchart.md.
Error path collection: When multiple diamonds share the same error endpoint, use a vertical collector line on the right side. All error branches connect horizontally to it (no arrowheads), collector feeds into error box. For complete XML templates and layout rules, see references/flowchart.md.
Structure Diagram
Nested levels must use different color classes:
- Outer level: lighter color (e.g.
c-blue) - Inner level: contrasting color (e.g.
c-teal,c-amber) - Same-color nesting destroys hierarchy — hard production rule
Container padding: Outer ≥ 24px, inner spacing ≥ 16px, label to edge ≥ 12px.
See references/structure.md for nesting rules and complete example.
Illustrative Diagram (abstract concepts)
Use when explaining how something works, showing data structures, or presenting spatial relationships.
Core principle: Draw the mechanism itself, not a "diagram about the mechanism." Color represents intensity, not category (warm = active, cool = static).
See references/illustrative.md for detailed specs and examples.
Step 6: Complete Dark Mode Rules
This is the biggest difference between production-grade and toy output.
Safe usage (auto-adapts to dark mode):
<!-- c-* classes: auto-handle light & dark -->
<g class="c-blue">
<rect .../>
<text class="th">Label</text> <!-- auto-correct color -->
</g>
<!-- CSS variables: auto-adapt -->
<line stroke="var(--b)" .../>
<rect fill="var(--bg2)" .../>
Dangerous usage (breaks in dark mode):
<!-- Forbidden: hardcoded hex on text -->
<text fill="#333333">Label</text> <!-- invisible on dark background -->
<!-- Forbidden: c-* background + hardcoded text -->
<g class="c-blue">
<rect .../>
<text fill="#333">Label</text> <!-- bg inverts, text doesn't -->
</g>
Safe hex values for line colors (mid-tone, readable in both modes):
#1D9E75 teal 400
#534AB7 purple 400 (for purple-semantic lines)
#185FA5 blue 600
#BA7517 amber 400
#993C1D coral 600
#888780 gray 400 (general neutral lines)
#E24B4A red 400 (error/fallback paths)
Line color rule — match source node's semantic color:
Connecting lines (arrows) must use the same color family as the source node (where the arrow starts), not the target. This creates a visual "information flows from" signal. Use the safe hex value corresponding to the source node's c-* class:
- Source is
c-teal→ line stroke#1D9E75 - Source is
c-purple→ line stroke#534AB7 - Source is
c-blue→ line stroke#185FA5 - Source is
c-amber→ line stroke#BA7517 - Source is
c-coral→ line stroke#993C1D - Source is
c-green→ line stroke#3B6D11 - Source is
c-grayor neutral → line stroke#888780 - Error/fallback paths → line stroke
#E24B4A
Exception: return/response lines always use #888780 (gray) regardless of source color.
Step 7: Line Crossing Prevention
Before drawing every line, perform these checks:
Checklist:
- List all rect bounding boxes:
{x, y, x+w, y+h} - Compare line path X, Y ranges against each box
- If line X range overlaps a rect X range, AND Y range overlaps → crossing detected
Mitigation options:
<!-- Option A: L-shaped detour around right side -->
<path d="M {x1} {y1} L {x1} {y_bypass} L {x2} {y_bypass} L {x2} {y2}"
fill="none" stroke="..." marker-end="url(#arrow)"/>
<!-- Option B: detour around left side -->
<path d="M {x1} {y1} L {x_left-20} {y1} L {x_left-20} {y2} L {x2} {y2}"
fill="none" stroke="..." marker-end="url(#arrow)"/>
<!-- Option C: symbolic cross-reference (when routing is too complex) -->
<!-- Place matching letter markers at source and target -->
<text class="ts" x="{x}" y="{y}">→ A</text>
Sequence diagram special rule: Messages are strictly horizontal, never diagonal. If two participants aren't adjacent, the message is still horizontal, just spanning farther.
Step 8: Post-Draw Checklist (7 items, covering the most common failures)
Check in order — don't skip:
- Root
<svg>hasxmlns="http://www.w3.org/2000/svg";<defs>contains<style>block (including dark-mode@media) - viewBox H = max(element y + height) + 40 — computed, not estimated
- All
<text>has a class (th/ts/t); text inside nodes hasdominant-baseline="central" - All
<path>lines havefill="none" - Single
<svg>element per diagram, withrole="img"+<title>+<desc> - Node position overlap check: for every pair of shape elements (rect, polygon, circle, ellipse), verify bounding boxes do not overlap. Compare
{x, x+w}and{y, y+h}ranges — if both X and Y ranges overlap, the nodes collide and one must be repositioned. Pay special attention to decision diamonds: their bounding box is wider than it appears (cx ± half_w), and adjacent rects must start aftercx + half_w. - Line connectivity check: every
<line>and<path>must connect to a valid target element (a rect, circle, or another line endpoint). No line should point into empty space. For error/fallback paths from decision diamonds, verify the error line reaches an error box or a collector line that leads to one.
Other rules (text not overflowing rects, line colors using safe hex, lines not crossing nodes) are naturally enforced during drawing and don't need a separate post-check.
Reference File Index
| File | Content | When to Read |
|---|---|---|
references/sequence.md |
Sequence diagram spec + Y-coordinate lookup tables + example | Drawing sequence diagrams |
references/flowchart.md |
Flowchart spec + node sizing + decision diamond layout + example | Drawing flowcharts |
references/structure.md |
Structure diagram spec + nesting rules + example | Drawing architecture/component diagrams |
references/state-machine.md |
State machine spec + initial/final state templates + example | Drawing state transitions, lifecycles |
references/timeline.md |
Timeline spec + event marker/card templates + example | Drawing chronological events, milestones |
references/erd.md |
mermaid.js ERD template + dark mode adaptation | Drawing ERDs |
references/interactive.md |
HTML interactive widget templates (slider, stepper, click) | When interactive controls needed |
references/illustrative.md |
Illustrative diagram spec (abstract concept spatial metaphor) | Explaining principles/mechanisms |
references/embedded-styles.md |
Complete CSS style block for SVG <defs> |
Required for every diagram |
PNG Export
After saving the SVG, convert to @2x PNG for sharing on platforms that don't render SVG:
node {skillDir}/scripts/svg2png.mjs <svg-path> [options]
Options:
-s, --scale <n>— Scale factor (default: 2)-o, --output <path>— Custom output path
Prerequisites: run cd {skillDir}/scripts && npm install once to install sharp.
Output Instructions
- SVG must be self-contained: embed
<style>(copy fromreferences/embedded-styles.md, keep only usedc-*classes) +xmlns - If
visualize:show_widgetis available, pass the full SVG code; otherwise write to.svgfile and useopencommand to view in browser - HTML interactive diagrams: pass the complete HTML fragment (no DOCTYPE/html/body tags)
- Add text context before and after the diagram to explain what it shows