Diagram Design
Create visual diagrams as self-contained HTML files with inline SVG and CSS, following an opinionated editorial design system.
Thirteen diagram types. One shared design system, complexity budget, and taste gate. Type-specific conventions live in references/ and are loaded only when you pick a type.
0. First-time setup — style guide gate
Before generating your first diagram in a new project, verify the style guide has been customized.
Open references/style-guide.md and check the default tokens. If they're still the shipped defaults (paper #faf7f2, ink #1c1917, accent #b5523a rust), pause and ask the user:
"This is your first diagram in this project. The style guide is still at the default (neutral stone + rust). Do you want to customize it to match your brand first? Options: (a) run onboarding — I'll pull colors and fonts from your website, (b) paste your tokens manually, (c) proceed with the default for now."
Then branch:
- (a) → follow
references/onboarding.md to fetch the site, extract palette + fonts, propose a diff, and write style-guide.md.
- (b) → accept the user's tokens and write them into
style-guide.md under a new "Custom tokens" section.
- (c) → proceed; optionally remind the user they can run onboarding later.
Once the style guide has been customized (or the user explicitly opted for default), skip this gate on subsequent runs. A simple way to detect customization: if the accent value in style-guide.md differs from #b5523a, assume custom.
Don't silently ship default-skinned diagrams into a branded project — that's the failure mode this gate exists to prevent.
1. Philosophy
The highest-quality move is usually deletion.
From .impeccable.md: "Confident restraint. Earn every element. One color accent, two families, a small spacing vocabulary. If removing it wouldn't hurt the page, remove it."
Applied to diagrams:
- Every node represents a distinct idea. Two nodes that always travel together are one node.
- Every connection carries information. If the relationship is obvious from layout, remove the line.
- Coral is editorial, not a flag. 1–2 focal nodes per diagram. Using it on 5 nodes erases the signal.
- The diagram isn't done when everything is added. It's done when nothing can be removed.
Target density: 4/10. Enough to be technically complete. Not so dense it needs a guide. Above 9 nodes, it's probably two diagrams.
2. When to Use
Use for any of the 13 diagram types (§3) when a reader will learn more from a visual than from prose, a table, or a bulleted list.
Don't use for:
- Quick unicode diagrams → use wiretext.
- Lists of things → table or bullets.
- Simple before/after → table.
- One-shape "diagrams" → just write the sentence.
Before drawing, ask: Would the reader learn more from this than from a well-written paragraph? If no, don't draw.
3. Diagram Types
Selection guide
Rules of thumb:
- If a 3-column table communicates the same thing, pick the table.
- If you're combining two types, pick the dominant axis — don't hybridize grammars.
- If you're past the complexity budget (§7), split into an overview + detail.
Always load the relevant references/type-*.md before drawing — it contains layout conventions, anti-patterns, and example files for that type.
4. Universal Anti-patterns
These mark "AI slop" diagrams of any type:
| Anti-pattern | Why it fails |
|---|
| Dark mode + cyan/purple glow | Looks "technical" without design decisions |
| JetBrains Mono as blanket "dev" font | Mono is for technical content — ports, commands, URLs. Names go in Geist sans. |
| Identical boxes for every node | Erases hierarchy |
| Legend floating inside the diagram area | Collides with nodes |
| Arrow labels with no masking rect | Bleeds through the line |
Vertical writing-mode text on arrows | Unreadable |
| 3 equal-width summary cards as default | Generic grid — vary widths |
| Shadow on any element | Shadows are out. Borders are in. |
rounded-2xl on boxes | Max radius 6–10px or none |
| Coral on every "important" node | Coral is 1–2 editorial accents, not a signaling system |
Type-specific anti-patterns live in each references/type-*.md.
5. Design System
The design system is skinnable. All colors, typography, and tokens live in a single source of truth — references/style-guide.md. This file describes semantic roles (paper, ink, muted, accent, link, …). The default skin is littlemight.com (warm paper, ink, coral); to apply your own brand, either edit style-guide.md directly or run the URL-based flow described in references/onboarding.md.
When specs below or in type references mention "ink", "accent", "muted", etc., look up the current hex value in style-guide.md.
Semantic roles (at a glance)
| Role | Purpose |
|---|
paper, paper-2 | Page bg and container bg |
ink | Primary text / stroke |
muted, soft | Secondary text, default arrows, sublabels |
rule, rule-solid | Hairline borders |
accent, accent-tint | 1–2 focal elements per diagram |
link | HTTP/API calls, external arrows |
Focal rule: accent goes on 1–2 elements max. Everything else is ink / muted / soft. If you're tempted to accent 4 things, you haven't decided what's focal yet.
Node type → treatment
| Type | Fill | Stroke |
|---|
| Focal (1–2 max) | accent-tint | accent |
| Backend / API / Step | white | ink |
| Store / State | ink @ 0.05 | muted |
| External / Cloud | ink @ 0.03 | ink @ 0.30 |
| Input / User | muted @ 0.10 | soft |
| Optional / Async | ink @ 0.02 | ink @ 0.20 dashed 4,3 |
| Security / Boundary | accent @ 0.05 | accent @ 0.50 dashed 4,4 |
Typography (summary — full spec in style-guide.md)
- Title — Instrument Serif, 1.75rem, 400 — H1 only
- Node name — Geist (sans), 12px, 600 — human-readable labels
- Sublabel — Geist Mono, 9px — ports, URLs, field types
- Eyebrow / tag — Geist Mono, 7–8px, uppercase, tracked — type tags, axis labels
- Arrow label — Geist Mono, 8px — annotation on arrows
- Editorial aside — Instrument Serif italic, 14px — callouts only
Mono is for technical content. Names are Geist sans. Page title is Instrument Serif. Italic Instrument Serif is reserved for annotation callouts. Never JetBrains Mono as a blanket "dev" font.
html
1<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
6. Core SVG Primitives
Universal building blocks. Type-specialized primitives (lifeline, activation bar, region) live in the relevant references/type-*.md. Optional primitives:
Background
svg
1<defs>
2 <pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
3 <circle cx="1" cy="1" r="0.9" fill="rgba(11,13,11,0.10)"/>
4 </pattern>
5</defs>
6<rect width="100%" height="100%" fill="#f5f4ed"/>
7<rect width="100%" height="100%" fill="url(#dots)" opacity="0.6"/>
Arrow markers (define all three, always)
svg
1<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
2 <polygon points="0 0, 8 3, 0 6" fill="#52534e"/>
3</marker>
4<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
5 <polygon points="0 0, 8 3, 0 6" fill="#f7591f"/>
6</marker>
7<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
8 <polygon points="0 0, 8 3, 0 6" fill="#1a70c7"/>
9</marker>
| Arrow | Stroke | When |
|---|
| Default | muted #52534e | Internal, generic |
| Accent | coral #f7591f | Primary / highlighted / headline |
| Link-blue | #1a70c7 | HTTP/API calls, external systems |
| Dashed | stroke-dasharray="5,4" + any color | Optional, passive, return, async |
Draw arrows before boxes so z-order puts lines behind nodes.
Node box — full pattern
svg
1<!-- 1. Opaque paper mask — prevents arrows bleeding through transparent fills -->
2<rect x="X" y="Y" width="W" height="H" rx="6" fill="#f5f4ed"/>
3<!-- 2. Styled box -->
4<rect x="X" y="Y" width="W" height="H" rx="6" fill="FILL" stroke="STROKE" stroke-width="1"/>
5<!-- 3. Rectangular type tag (rx=2, NOT a pill) -->
6<rect x="X+8" y="Y+6" width="28" height="12" rx="2" fill="transparent" stroke="STROKE@0.40" stroke-width="0.8"/>
7<text x="X+22" y="Y+15" fill="STROKE@0.8" font-size="7" font-family="'Geist Mono', monospace"
8 text-anchor="middle" letter-spacing="0.08em">API</text>
9<!-- 4. Node name (Geist sans — human-readable) -->
10<text x="CX" y="CY+2" fill="#0b0d0b" font-size="12" font-weight="600"
11 font-family="'Geist', sans-serif" text-anchor="middle">Node Name</text>
12<!-- 5. Technical sublabel (Geist Mono) -->
13<text x="CX" y="CY+18" fill="#52534e" font-size="9"
14 font-family="'Geist Mono', monospace" text-anchor="middle">tech:port</text>
Arrow labels — always mask
Every arrow label needs an opaque rect behind it. Without one it bleeds through the line.
svg
1<rect x="MID_X-18" y="ARROW_Y-12" width="36" height="12" rx="2" fill="#f5f4ed"/>
2<text x="MID_X" y="ARROW_Y-3" fill="#65655c" font-size="8"
3 font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.06em">WRITE</text>
Rules: ≤14 characters, all-caps, centered on segment midpoint, 8–10px above line. Never writing-mode vertical.
Legend — horizontal strip at the bottom
Never put the legend inside the diagram area. Place as a horizontal strip after all nodes, with a hairline separator:
svg
1<line x1="30" y1="LEGEND_Y-8" x2="VIEWBOX_W-30" y2="LEGEND_Y-8"
2 stroke="rgba(11,13,11,0.10)" stroke-width="0.8"/>
3<text x="30" y="LEGEND_Y+8" fill="#52534e" font-size="8" font-family="'Geist Mono', monospace"
4 letter-spacing="0.14em">LEGEND</text>
5<!-- Items — horizontal row, ~160px apart -->
Expand SVG viewBox height by ~60px.
7. Layout & Spacing
4px grid
All values — font sizes, padding, node dimensions, gaps, x/y coords — divisible by 4. Non-negotiable.
| Category | Allowed values |
|---|
| Font sizes | 8, 12, 16, 20, 24, 28, 32, 40 |
| Node width / height | 80, 96, 112, 120, 128, 140, 144, 160, 180, 200, 240, 320 |
| x / y coordinates | multiples of 4 |
| Gap between nodes | 20, 24, 32, 40, 48 |
| Padding inside boxes | 8, 12, 16 |
| Border radius | 4, 6, 8 |
Exempt: stroke widths (0.8, 1, 1.2), opacity values, and the 22×22 dot-pattern.
Quick check: if a coordinate ends in 1, 2, 3, 5, 6, 7, 9 — fix it.
Complexity budget (per diagram)
| Limit | Rule |
|---|
| Max nodes | 9 |
| Max arrows / transitions | 12 |
| Max coral elements | 2 |
| Max lifelines (sequence) | 5 |
| Max lanes (swimlane) | 5 |
| Max items (quadrant) | 12 |
| Max entities (ER) | 8 |
| Max nesting levels (nested) | 6 |
| Max tree depth | 4 |
| Max layers (layer stack) | 6 |
| Max circles (venn) | 3 |
| Max layers (pyramid) | 6 |
| Max annotation callouts | 2 |
If you exceed, split into two diagrams (overview + detail).
Page layout
- Header — eyebrow (Geist Mono), title (Instrument Serif), optional subtitle (Geist muted).
- Diagram container —
#efeee5 bg, 1px rgba(11,13,11,0.12) border, 8px radius, overflow-x: auto.
- Summary cards — 2–3 col grid with varied widths (e.g.,
1.1fr 1fr 0.9fr).
- Footer — colophon in Geist Mono, muted, hairline top border.
8. Summary Card Pattern
Don't use 3 identical generic cards. Vary the treatment:
html
1<div class="card">
2 <p class="eyebrow">SECTION LABEL</p>
3 <div class="card-header">
4 <span class="card-dot coral"></span>
5 <h3>Card Title</h3>
6 </div>
7 <ul><li>Item</li></ul>
8</div>
Rules:
background: #ffffff (not paper — slight lift without shadow)
border: 1px solid rgba(11,13,11,0.12)
border-radius: 6px, padding: 1.25rem
- No
box-shadow
- Card dots: 7px,
border-radius: 50% — ink / muted / coral / link / soft variants
9. Pre-Output Checklist (Taste Gate)
Run before producing any diagram.
Type fit:
Remove test:
Signal:
Technical:
Typography:
10. Templates & Variants
Every diagram ships in three variants (see assets/):
| Variant | File pattern | When to use |
|---|
| Minimal light (default) | template.html, example-<type>.html | Screenshot-ready. Diagram + title. Warm paper. |
| Minimal dark | template-dark.html, example-<type>-dark.html | Dark mode sites, slides, high-contrast posts. |
| Full editorial | template-full.html, example-<type>-full.html | Long-form posts where the diagram is the hero. |
Sketchy variant (optional, applied to any of the above) — see primitive-sketchy.md. SVG turbulence filter wobbles strokes for a hand-drawn feel. Good for essays, not for technical docs.
To create a new diagram
- Copy the variant closest to what you want (
template.html for minimal, template-full.html for cards).
- Load the matching
references/type-<name>.md for layout conventions.
- Replace the eyebrow, h1, and SVG body.
- Run the §9 taste gate.
11. Output
Always produce a single self-contained .html file:
- Embedded CSS (no external except Google Fonts)
- Inline SVG (no external images)
- No JavaScript required
Renders correctly in any modern browser.