# CSS Patterns for Diagrams Reusable patterns for layout, connectors, theming, and visual effects in self-contained HTML diagrams. ## Theme Setup Always define both light and dark palettes via custom properties. Start with whichever fits the chosen aesthetic, ensure both work. ```css :root { --font-body: 'Outfit', system-ui, sans-serif; --font-mono: 'Space Mono', 'SF Mono', Consolas, monospace; --bg: #f8f9fa; --surface: #ffffff; --surface-elevated: #ffffff; --border: rgba(0, 0, 0, 0.08); --border-bright: rgba(0, 0, 0, 0.15); --text: #1a1a2e; --text-dim: #6b7280; --accent: #0891b2; --accent-dim: rgba(8, 145, 178, 0.1); /* Semantic accents for diagram elements */ --node-a: #0891b2; --node-a-dim: rgba(8, 145, 178, 0.1); --node-b: #059669; --node-b-dim: rgba(5, 150, 105, 0.1); --node-c: #d97706; --node-c-dim: rgba(217, 119, 6, 0.1); } @media (prefers-color-scheme: dark) { :root { --bg: #0d1117; --surface: #161b22; --surface-elevated: #1c2333; --border: rgba(255, 255, 255, 0.06); --border-bright: rgba(255, 255, 255, 0.12); --text: #e6edf3; --text-dim: #8b949e; --accent: #22d3ee; --accent-dim: rgba(34, 211, 238, 0.12); --node-a: #22d3ee; --node-a-dim: rgba(34, 211, 238, 0.12); --node-b: #34d399; --node-b-dim: rgba(52, 211, 153, 0.12); --node-c: #fbbf24; --node-c-dim: rgba(251, 191, 36, 0.12); } } ``` ## Background Atmosphere Flat backgrounds feel dead. Use subtle gradients or patterns. ```css /* Radial glow behind focal area */ body { background: var(--bg); background-image: radial-gradient(ellipse at 50% 0%, var(--accent-dim) 0%, transparent 60%); } /* Faint dot grid */ body { background-color: var(--bg); background-image: radial-gradient(circle, var(--border) 1px, transparent 1px); background-size: 24px 24px; } /* Diagonal subtle lines */ body { background-color: var(--bg); background-image: repeating-linear-gradient( -45deg, transparent, transparent 40px, var(--border) 40px, var(--border) 41px ); } /* Gradient mesh (pick 2-3 positioned radials) */ body { background: var(--bg); background-image: radial-gradient(at 20% 20%, var(--node-a-dim) 0%, transparent 50%), radial-gradient(at 80% 60%, var(--node-b-dim) 0%, transparent 50%); } ``` ## Link Styling **Never rely on browser default link colors.** The default blue (`#0000EE`) has poor contrast on dark backgrounds. Style links with `color: var(--accent)` and keep underlines for discoverability. On dark backgrounds, use bright accents (`#22d3ee`, `#34d399`, `#fbbf24`). On light backgrounds, use deeper tones (`#0891b2`, `#059669`, `#d97706`). ## Section / Card Components The fundamental building block. A colored card representing a system component, pipeline step, or data entity. **IMPORTANT: Never use `.node` as a CSS class name.** Mermaid.js internally uses `.node` on its SVG `` elements with `transform: translate(x, y)` for positioning. Any page-level `.node` styles (hover transforms, box-shadows, transitions) will leak into Mermaid diagrams and break their layout. Use `.ve-card` instead (namespaced to avoid collisions with CSS frameworks like Bootstrap/Tailwind that also use `.card`). ```css .ve-card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 16px 20px; position: relative; } /* Colored accent border (left or top) */ .ve-card--accent-a { border-left: 3px solid var(--node-a); } /* --- Depth tiers: vary card depth to signal importance --- */ /* Elevated: KPIs, key sections, anything that should pop */ .ve-card--elevated { background: var(--surface-elevated); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04); } /* Recessed: code blocks, secondary content, detail panels */ .ve-card--recessed { background: color-mix(in srgb, var(--bg) 70%, var(--surface) 30%); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.06); border-color: var(--border); } /* Hero: executive summaries, focal elements — demands attention */ .ve-card--hero { background: color-mix(in srgb, var(--surface) 92%, var(--accent) 8%); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04); border-color: color-mix(in srgb, var(--border) 50%, var(--accent) 50%); } /* Glass: special-occasion overlay effect (use sparingly) */ .ve-card--glass { background: color-mix(in srgb, var(--surface) 60%, transparent 40%); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-color: rgba(255, 255, 255, 0.1); } /* Section label (monospace, uppercase, small) */ .ve-card__label { font-family: var(--font-mono); font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.5px; color: var(--node-a); margin-bottom: 10px; display: flex; align-items: center; gap: 8px; } /* Colored dot indicator */ .ve-card__label::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: currentColor; } ``` ## Code Blocks Code blocks need explicit whitespace preservation and a max-height constraint. Without these, code runs together and long files overwhelm the page. ### Basic Pattern ```css .code-block { font-family: var(--font-mono); font-size: 13px; line-height: 1.5; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; overflow-x: auto; /* CRITICAL: preserve line breaks and indentation */ white-space: pre-wrap; word-break: break-word; } /* Constrain height for long code */ .code-block--scroll { max-height: 400px; overflow-y: auto; } ``` ```html
// Your code here
function example() {
  return true;
}
``` ### With File Header ```css .code-file { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; } .code-file__header { display: flex; align-items: center; gap: 8px; padding: 10px 16px; background: var(--surface); border-bottom: 1px solid var(--border); font-family: var(--font-mono); font-size: 12px; color: var(--text-dim); } .code-file__body { font-family: var(--font-mono); font-size: 13px; line-height: 1.5; padding: 16px; background: var(--surface-elevated); white-space: pre-wrap; word-break: break-word; max-height: 500px; overflow: auto; } ``` ```html
src/extension.ts
export function activate() {
  // ...
}
``` ### Implementation Plans: Don't Dump Full Files For implementation plans and architecture docs, **don't display entire source files inline**. Instead: 1. **Show structure, not code:** ```html
src/extension.ts
``` 2. **Use collapsible sections for full code:** ```html
Full implementation (87 lines)
...
``` 3. **Show key snippets only:** ```html

The core logic intercepts task completion:

pi.on("agent_end", async () => {
     const summary = generateSummary(workEntries);
     boomerangComplete = true;
   });
``` **Anti-patterns:** - Displaying full source files inline (100+ lines overwhelming the page) - Code blocks without `white-space: pre-wrap` (code runs together into unreadable wall) - No height constraint on long code (page becomes endless scroll) If someone needs the full file, put it in a collapsible section or link to it. ## Directory Tree For file structures, use `
` with monospace + `white-space: pre`. Tree connectors (`├──`, `└──`, `│`) only work when vertically aligned — they become noise if text wraps.

```css
.dir-tree {
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.7;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px 20px;
  overflow-x: auto;
  white-space: pre;
}

.dir-tree .ann { color: var(--text-dim); font-size: 11px; font-style: italic; }
.dir-tree .hl  { color: var(--accent); font-weight: 600; }
```

```html
my-project/
├── src/
│   ├── index.ts       — entry point
│   ├── services/
│   │   └── api.py     (142 lines)
│   └── utils/
├── tests/            (14 test files)
└── README.md
``` For labeled trees, wrap in a card. For side-by-side comparisons, put two cards in a grid: ```css .dir-tree-card { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; } .dir-tree-card__header { display: flex; align-items: center; gap: 8px; padding: 10px 16px; background: var(--surface); border-bottom: 1px solid var(--border); font-family: var(--font-mono); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.5px; } .dir-tree-card .dir-tree { border: none; border-radius: 0; } /* Side-by-side: two .dir-tree-card in a grid */ .dir-compare { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; } @media (max-width: 900px) { .dir-compare { grid-template-columns: 1fr; } } ``` **Never** render tree connectors inside wrapping text (`white-space: normal`), flex children, or grid items — the vertical pipes lose alignment and the hierarchy becomes unreadable. ## Overflow Protection Grid and flex children default to `min-width: auto`, which prevents them from shrinking below their content width. Long text, inline code badges, and non-wrapping elements will blow out containers. ### Global rules ```css /* Every grid/flex child must be able to shrink */ .grid > *, .flex > *, [style*="display: grid"] > *, [style*="display: flex"] > * { min-width: 0; } /* Long text wraps instead of overflowing */ body { overflow-wrap: break-word; } ``` ### Side-by-side comparison panels ```css .comparison { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } .comparison > * { min-width: 0; overflow-wrap: break-word; } @media (max-width: 768px) { .comparison { grid-template-columns: 1fr; } } ``` ### Never use `display: flex` on `
  • ` for marker characters Using `display: flex` on a list item to position a `::before` marker creates an anonymous flex item for the remaining text content. That anonymous flex item gets `min-width: auto` and you **cannot** set `min-width: 0` on anonymous boxes. Lines with many inline `` badges will overflow their container with no CSS fix possible. Use absolute positioning for markers instead: ```css /* WRONG — causes overflow with inline code badges */ li { display: flex; align-items: baseline; gap: 6px; } li::before { content: '›'; flex-shrink: 0; } /* RIGHT — text wraps normally */ li { padding-left: 14px; position: relative; } li::before { content: '›'; position: absolute; left: 0; } ``` ### List markers overlapping container borders By default, `list-style-position: outside` places list markers (bullets, numbers) outside the content box. When lists are inside bordered containers (cards, callout boxes), the markers can overlap or extend beyond the border. ```css /* WRONG — markers overlap container border */ .card ol, .card ul { padding-left: 20px; /* Not enough for outside markers */ } /* RIGHT — use inside positioning */ .card ol, .card ul { list-style-position: inside; } /* OR — adequate padding for outside markers */ .card ol, .card ul { padding-left: 2em; /* ~32px gives room for markers */ } /* OR — custom markers with absolute positioning (most control) */ .card ol { list-style: none; padding-left: 0; counter-reset: item; } .card ol li { counter-increment: item; padding-left: 2em; position: relative; } .card ol li::before { content: counter(item) "."; position: absolute; left: 0; color: var(--accent); font-weight: 600; } ``` **Rule of thumb:** Any `
      ` or `
        ` inside a bordered container needs either `list-style-position: inside` or `padding-left: 2em` minimum. The default 20px padding is not enough for outside-positioned markers. ## Mermaid Containers Mermaid diagrams have two common layout issues: they render too small to read, and they left-align in their container leaving awkward dead space (especially for narrow vertical flowcharts). ### Centering (Required) Mermaid SVGs render at a fixed size based on content. Without explicit centering, they default to top-left alignment. **Always center Mermaid diagrams** — narrow vertical flowcharts look particularly bad when left-aligned in a wide container. ```css /* WRONG — diagram hugs left edge */ .mermaid-container { padding: 24px; border: 1px solid var(--border); } /* RIGHT — diagram centers in container */ .mermaid-wrap { display: flex; justify-content: center; align-items: flex-start; /* or center for shorter diagrams */ padding: 24px; border: 1px solid var(--border); } ``` ### Scaling Small Diagrams Mermaid sizes diagrams based on content, not container. Complex diagrams with many nodes render small to fit everything, leaving the text nearly unreadable. Three fixes: **1. Increase fontSize in themeVariables** (most effective): ```javascript mermaid.initialize({ theme: 'base', themeVariables: { fontSize: '18px', // default is 16px, bump to 18-20px for complex diagrams } }); ``` **2. CSS zoom** for diagrams that still render too small: ```css .mermaid-wrap--scaled .mermaid { zoom: 1.3; } ``` **3. Constrain container width** so the diagram doesn't float in dead space: ```css .mermaid-wrap--constrained { max-width: 800px; margin: 0 auto; } ``` **Rule of thumb:** If the diagram has 10+ nodes or the text is smaller than 12px rendered, increase fontSize to 18-20px or apply CSS zoom. ### Zoom Controls Add zoom controls to every `.mermaid-wrap` container for complex diagrams. **Small diagrams in slides.** If a diagram has fewer than ~7 nodes with no branching, it will render tiny in a full-viewport slide container. For simple linear flows (A → B → C → D), use CSS pipeline cards instead of Mermaid — see `slide-patterns.md` "CSS Pipeline Slide." Reserve Mermaid for complex graphs where automatic edge routing is actually needed. ### Full Pattern ```css .mermaid-wrap { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 32px 24px; overflow: auto; /* CRITICAL: center the diagram both horizontally and vertically */ display: flex; justify-content: center; align-items: center; /* Prevent vertical flowcharts from compressing into unreadable thumbnails */ min-height: 400px; } /* For shorter diagrams that don't need the full height */ .mermaid-wrap--compact { min-height: 200px; } /* For very tall vertical flowcharts */ .mermaid-wrap--tall { min-height: 600px; } .zoom-controls { position: absolute; top: 8px; right: 8px; display: flex; gap: 2px; z-index: 10; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 2px; } .zoom-controls button { width: 28px; height: 28px; border: none; background: transparent; color: var(--text-dim); font-family: var(--font-mono); font-size: 14px; cursor: pointer; border-radius: 4px; display: flex; align-items: center; justify-content: center; transition: background 0.15s ease, color 0.15s ease; } .zoom-controls button:hover { background: var(--border); color: var(--text); } .mermaid-wrap { cursor: grab; } .mermaid-wrap.is-panning { cursor: grabbing; user-select: none; } /* Multi-diagram structure */ .diagram-shell { position: relative; } .diagram-shell__hint { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); margin-bottom: 8px; opacity: 0.7; } .mermaid-viewport { position: relative; overflow: hidden; width: 100%; height: 100%; min-height: 300px; } .mermaid-canvas { position: absolute; top: 0; left: 0; } .zoom-label { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); padding: 0 6px; white-space: nowrap; } ``` **How the new zoom/pan engine works:** The SVG is rendered into `.mermaid-canvas` which is absolutely positioned inside `.mermaid-viewport`. Zooming sets the SVG's `width` and `height` styles directly. Panning applies `transform: translate()` to the canvas. The viewport has `overflow: hidden` to clip the panned content. This approach avoids CSS `zoom` (which had cross-browser quirks) and gives precise control over the diagram's size and position. ### HTML ```html

        Ctrl/Cmd + wheel to zoom. Scroll to pan. Drag to pan when zoomed. Double-click to fit.

        Loading...
        ``` Use one `.diagram-shell` per diagram. The source Mermaid text lives in `