| name | avenire-visual-guidelines |
| description | Create Avenire-quality visual artifacts from terminal agents: polished SVG diagrams, mathematical visuals, charts, UI mockups, interactive explainers, physics simulations, and generative art. Use when asked to visualize, diagram, draw, create a chart/dashboard/mockup/widget, make an explainer, or generate HTML/SVG visual artifacts in pi, Codex, Grok Build, or other coding agents. |
Avenire visual guidelines for terminal agents
You are using Avenire's visual creation guidelines outside the Avenire chat runtime. Apply these rules when generating visual artifacts in pi, Codex, Grok Build, Claude Code, or any terminal coding agent.
Runtime adaptation
Avenire's original guidelines assume a show_widget tool with either widget.type: "spec" or widget.type: "code". In terminal agents, adapt as follows:
- If the host provides
show_widget, use it exactly as described below.
- If no widget tool exists, create a standalone artifact file instead:
- Raw SVG diagrams or illustrations: write an
.svg file.
- Interactive explainers, Chart.js visuals, dashboards, or UI mockups: write a self-contained
.html file.
- Primitive/spec artifacts: write the JSON spec to
.json, or translate it into equivalent HTML if the user needs something viewable in a browser.
- Prefer creating files over dumping large code in chat when the user asks to build, implement, or generate an artifact.
- Preserve the visual constraints below even outside Avenire: sentence case, compact labels, dark-mode-safe tokens/classes where applicable, no generic AI styling, no decorative gradients/shadows, no emoji, and careful SVG layout math.
- If the target project does not expose Avenire CSS variables/classes, define a minimal local theme in the artifact that maps the documented variables/classes to neutral light/dark-safe values. Keep the look flat, quiet, and native.
- Do not use or include any study-guideline skills. This skill is visual-guidelines only.
Source bundle
This global skill is generated from packages/ai/skills/sections/visual-guidelines/ in the Avenire repo. It intentionally excludes study-guidelines.
Imagine — Visual Creation Suite
Modules
Call visualize_read_me again with the relevant visual modules when you need more specific guidance:
diagram — SVG flowcharts, structural diagrams, illustrative diagrams
mockup — UI mockups, forms, cards, dashboards. Prefer widget.type: "spec" primitives unless pixel-specific HTML is required.
interactive — interactive explainers with controls. Prefer widget.type: "spec" for static/structured explainers; use raw HTML for controls and custom JS.
chart — charts and data analysis. Use widget.type: "spec" first for bar, line, area, stats, tables, callouts, and dashboard/report layouts. Raw Chart.js is only for unsupported chart types or imperative chart interaction.
art — illustration and generative art
physics — physics simulations, motion, forces, energy, and time-evolving systems
Pick the closest fit. Each module includes the relevant design guidance.
Default artifact rule: if the answer includes a report, dashboard, comparison matrix, status summary, card grid, metric row, chart, callout, progress readout, or table that should stand alone, call show_widget with widget: { type: "spec", spec: ... }. Plain markdown is fine for a small table embedded in a normal prose answer; spec tables are for artifact-style reports where scanning and layout matter.
Complexity budget — hard limits:
- Box subtitles: ≤5 words. Detail goes in click-through (
sendPrompt) or the prose below — not the box.
- Colors: ≤2 ramps per diagram. If colors encode meaning (states, tiers), add a 1-line legend. Otherwise use one neutral ramp.
- Horizontal tier: ≤4 boxes at full width (~140px each). 5+ boxes → shrink to ≤110px OR wrap to 2 rows OR split into overview + detail diagrams.
If you catch yourself writing "click to learn more" in prose, the diagram itself must ACTUALLY be sparse. Don't promise brevity then front-load everything.
You create rich visual content — first-class primitive canvases, SVG diagrams/illustrations, and HTML interactive widgets — that renders inline in conversation. The best output feels like a natural extension of the chat.
Core Design System
These rules apply to ALL use cases.
Philosophy
- Seamless: Users shouldn't notice where claude.ai ends and your widget begins.
- Primitive-first: For canvas-style artifacts, use
show_widget with widget: { type: "spec", spec: ... } first-class primitives. They render with Avenire's shadcn UI components and are the default for cards, metrics, tables, sections, charts, callouts, and structured reports.
- Raw-code escape hatch: Use
show_widget with widget: { type: "code", code: ... } raw HTML/SVG only when the widget needs custom drawing, custom interaction, canvas animation, imperative JS, mermaid, or third-party libraries.
- Compact but complete: Inline widgets should stay compact. Canvas artifacts may include concise headings, labels, callouts, and tables inside the widget when that content is part of the artifact.
- No duplicated prose: Full explanations belong in the chat response. The widget may contain short artifact text that helps the visual stand alone.
- Use the system as-is: do not invent your own styling language for primitive widgets. Reuse the provided primitive nodes and theme tokens. Treat them as a contract, not inspiration.
Streaming
Output streams token-by-token. Structure code so useful content appears early.
- HTML:
<style> (short) → content HTML → <script> last.
- SVG:
<defs> (markers) → visual elements immediately.
- Prefer inline
style="..." over <style> blocks — inputs/controls must look correct mid-stream.
- Keep
<style> under ~15 lines. Interactive widgets with inputs and sliders need more style rules — that's fine, but don't bloat with decorative CSS.
- Gradients, shadows, and blur flash during streaming DOM diffs. Use solid flat fills instead.
Rules
- No
<!-- comments --> or /* comments */ (waste tokens, break streaming)
- No font-size below 11px
- No emoji — use CSS shapes or SVG paths
- No gradients, drop shadows, blur, glow, or neon effects
- No hand-authored UI colors. Do not pick colors for primitive widgets. Use
tone and built-in chart defaults. Raw SVG/canvas may use CSS variables and documented SVG classes; only use hardcoded colors for domain-specific physical illustrations where the color carries meaning.
- Classes first. If a provided classname solves the problem, use the classname. Do not recreate the same look with inline color/fill/stroke declarations.
- Do not restyle core components from scratch. Inputs, buttons, sliders, cards, pills, metric blocks, and diagram nodes should follow the prescribed structure. If a primitive exists, use it instead of recreating it in HTML.
- Layout styles are allowed; appearance styles are not. Use inline CSS for positioning, spacing, sizing, and grid/flex layout. Do not use inline CSS to invent new visual design for components.
- No dark/colored backgrounds on outer containers (transparent only — host provides the bg)
- Typography: The default font is var(--font-sans). For the rare editorial/blockquote moment, use
font-family: var(--font-serif).
- Headings: h1 = 22px, h2 = 18px, h3 = 16px — all
font-weight: 500. Heading color is pre-set to var(--color-text-primary) — don't override it. Body text = 16px, weight 400, line-height: 1.7. Two weights only: 400 regular, 500 bold. Never use 600 or 700 — they look heavy against the host UI.
- Sentence case always. Never Title Case, never ALL CAPS. This applies everywhere including SVG text labels and diagram headings.
- No mid-sentence bolding, including in your response text around the tool call. Entity names, class names, function names go in
code style not bold. Bold is for headings and labels only.
- The widget container is
display: block; width: 100%. Your HTML fills it naturally — no wrapper div needed. Just start with your content directly. If you want vertical breathing room, add padding: 1rem 0 on your first element.
- Never use
position: fixed — the iframe viewport sizes itself to your in-flow content height, so fixed-positioned elements (modals, overlays, tooltips) collapse it to . For modal/overlay mockups: wrap everything in a normal-flow and put the modal inside — it's a faux viewport that actually contributes layout height.
Quality gate
Before calling show_widget, run this mental review. If any item fails, revise the widget first.
- Contrast: every text label, axis label, stroke, dot, and control readout must be readable in both light and dark mode. Do not draw near-black marks on a near-black canvas or near-white marks on a white canvas. Use
--color-text-primary, --color-text-secondary, --color-border-secondary, semantic tokens, or the canvas theme variables instead.
- Ink density: the main visual should have enough visible structure to be understood at a glance. Avoid tiny sparse dots, hairline strokes, and unlabeled marks as the only signal. Primary marks should normally be at least 2px strokes or 5px dots in CSS pixels; secondary guides can be lighter but still visible.
- Native controls: buttons, sliders, inputs, and readouts must look like the host UI. Use bare form tags or the documented component recipes. Do not make bright custom-filled buttons unless the host component already provides that treatment.
- Alignment: controls must sit in clear rows with labels, inputs, and values aligned. Avoid loose labels floating beside sliders or values detached from the variable they describe.
- Responsive fit: no text should overlap, clip, or depend on one fixed canvas size. Use
ResizeObserver, CSS grid with minmax(0, 1fr), and measured canvas layout.
- Self-check labels: if a user cannot tell what the x-axis, y-axis, markers, and current state mean without reading the chat response, add short in-widget labels.
- No dim placeholders: a widget must not look like a loading skeleton after it has rendered. Avoid low-opacity whole-widget styling, under-contrasted canvases, or empty panels with only faint marks.
CSS Variables
Backgrounds: --color-background-primary (white), -secondary (surfaces), -tertiary (page bg), -info, -danger, -success, -warning
Text: --color-text-primary (black), -secondary (muted), -tertiary (hints), -info, -danger, -success, -warning
Borders: --color-border-tertiary (0.15α, default), -secondary (0.3α, hover), -primary (0.4α), semantic -info/-danger/-success/-warning
Typography: --font-sans, --font-serif, --font-mono
Layout: --border-radius-md (8px), --border-radius-lg (12px — preferred for most components), --border-radius-xl (16px)
All auto-adapt to light/dark mode. These are the only allowed theme primitives. Do not invent additional color values.
The shared UI theme also exposes palette families through:
--color-text-default|gray|brown|orange|yellow|green|blue|purple|pink|red
--color-bg-default|gray|brown|orange|yellow|green|blue|purple|pink|red
--color-pill-default|gray|brown|orange|yellow|green|blue|purple|pink|red
Dark mode is mandatory — every color must work in both modes:
- In SVG: use the pre-built color classes (
c-default, c-gray, c-brown, c-orange, c-yellow, c-green, c-blue, c-purple, c-pink, c-red, plus aliases c-teal, c-amber, c-coral) for colored nodes — they handle light/dark mode automatically. Never write <style> blocks for colors.
- In SVG: every
<text> element needs a class (t, ts, th) — never omit fill or use fill="inherit". Inside a c-{color} parent, text classes auto-adjust to the ramp.
- In HTML: use the provided component styles and theme tokens only. Never hardcode colors like
#333, and do not invent your own palette choices.
- Mental test: if the background were near-black, would every text element still be readable?
sendPrompt(text)
A global function that sends a message to chat as if the user typed it. Use it when the user's next step benefits from Claude thinking. Handle filtering, sorting, toggling, and calculations in JS instead.
Links
<a href="https://..."> just works — clicks are intercepted and open the host's link-confirmation dialog. Or call openLink(url) directly.
When nothing fits
Pick the closest use case below and adapt. When nothing fits cleanly:
- Default to editorial layout if the content is explanatory
- Default to card layout if the content is a bounded object
- All core design system rules still apply
- Use
sendPrompt() for any action that benefits from Claude thinking
name: first-class-primitives
description: Use structured first-class widget primitives for polished canvas artifacts rendered with the Avenire shadcn UI system.
First-class widget primitives
Prefer show_widget with widget: { type: "spec", spec: ... } for canvas-style artifacts: debugging reports, docs canvases, learning dashboards, comparison cards, metric summaries, tables, charts, timelines, and structured explanations. The app renders spec widgets with first-class React components backed by @avenire/ui shadcn primitives, so the result inherits the host theme, spacing, cards, tables, badges, progress bars, and chart styling.
Use widget: { type: "code", code: ... } only when the visual needs custom SVG geometry, canvas drawing, imperative animation, DOM event handling, sliders, steppers, simulations, mermaid, or third-party libraries. If the artifact is mostly layout, text, metrics, tables, cards, or simple charts, use a spec widget.
Tool shape
Call show_widget with a widget object. For primitive widgets, use type: "spec" and put the primitive tree in spec.
{
"i_have_seen_read_me": true,
"title": "Websocket pool leak debug",
"widget": {
"type": "spec",
"spec": {
"title": "Websocket pool leak debug",
"description": "Incident view with request, connection, memory, and milestone evidence.",
"root": {
"type": "stack",
"gap": "lg",
"children": []
}
}
}
}
Available nodes
stack: vertical composition. Fields: children, optional gap (xs, sm, md, lg, xl).
grid: responsive grid. Fields: children, optional columns (1-4), optional gap.
section: open section with optional title, description, and children. Use to avoid a wall of identical cards.
card: bounded surface with optional title, description, tone, and children.
stat: metric card. Fields: label, value, optional delta, optional tone.
heading: text heading. Fields: text, optional level (1, 2, 3).
text: paragraph. Fields: text, optional tone, optional weight (regular, medium).
badge: compact label. Fields: text, optional tone.
callout: highlighted note. Fields: optional title, text, tone, children.
table: data table. Fields: headers, rows, optional caption.
chart: Recharts-backed chart. Fields: chartType (bar, line, area), data, indexKey, series, optional title.
progress: progress row. Fields: value from 0-100, optional .
Tones: default, muted, info, success, warning, danger.
Composition rules
- Lead with the artifact's answer: a title, a one-line description, then the most important metric, chart, or table.
- Mix open sections with cards. Do not wrap every block in a card.
- Use
grid for 2-4 stats or option cards. Use stack for narrative flow.
- Use
section for headings and grouping; use card for bounded objects that should feel like one unit.
- Keep card titles short and sentence case.
- Prefer one strong chart plus a compact table over several tiny charts.
- Round all displayed numbers before putting them into the JSON.
- Keep rows compact: tables should usually have 3-6 columns and fewer than 20 rows.
- Use semantic tones sparingly. Most nodes should be neutral; reserve
warning, danger, and success for meaning.
Good pattern
{
"title": "Resource leak incident",
"description": "Connections climb after deploy, then recover after rollback.",
"root": {
"type": "stack",
"gap": "lg",
"children": [
{
"type": "grid",
"columns": 3,
"children": [
{ "type": "stat", "label": "Peak open connections", "value": "8.4k", "delta": "+6.1k after deploy", "tone": "warning" },
{ "type": "stat", "label": "Failed requests", "value": "1.5k/hr", "delta": "Recovered after rollback", "tone": "danger" },
{ "type": "stat", "label": "Memory at baseline", "value": "08:08", "delta": "Hotfix stable", "tone": "success" }
]
},
{
"type": "chart",
"title": "Failed requests per hour",
"chartType": "area",
"indexKey": "time",
"series": [{ "dataKey": "failed", "label": "Failed requests" }],
"data": [
{ "time": "07:00", "failed": 0 },
{ "time": "07:16", "failed": 240 },
{ "time": "07:20", "failed": 1480 },
{ "time": "08:08", "failed": 12 }
]
},
{
"type": "section",
"title": "Milestones",
"children": [
{
"type": "table",
"headers": ["Time", "Event", "Interpretation"],
"rows": [
["07:10", "Deploy v2.14.0", "Connections begin rising"],
["07:16", "Alert fired", "Errors and p99 diverge"],
["07:20", "Rollback", "Connections and memory fall"]
]
}
]
}
]
}
}
Bad pattern
- Do not put a single sentence into a card by itself.
- Do not create a vertical stack of five identical cards.
- Do not use raw HTML for tables, badges, metric cards, or simple charts.
- Do not use decorative tones just to make the canvas colorful.
- Do not duplicate the same explanatory paragraph in chat and in the widget. The widget can contain concise labels and artifact text; the chat response should carry the full explanation.
UI components
Aesthetic
Use widget: { type: "spec", spec: ... } first for UI-like widgets. It renders with the host's shadcn primitives: cards, badges, tables, metric stats, sections, progress, callouts, and charts. Raw HTML is now the fallback for custom interaction, custom SVG/canvas, or controls that cannot be represented with primitives.
Flat, clean surfaces. Minimal borders. Generous whitespace. Avoid decorative gradients and shadows in primitive widgets. Everything should feel native to Avenire — like it belongs on the page, not embedded from somewhere else.
This section is prescriptive. Do not freestyle component styling. Use spec nodes when available; use the exact HTML recipes only when raw code widgets are necessary.
Hard rule: component CSS is for layout only. Do not invent new visual treatments for cards, controls, badges, pills, panels, or tables. If the request maps to a spec widget, use primitives. If it must be raw HTML, copy the closest pattern below.
Tokens
- Borders: always
0.5px solid var(--color-border-tertiary) (or -secondary for emphasis)
- Corner radius:
var(--border-radius-md) for most elements, var(--border-radius-lg) for cards
- Cards: white bg (
var(--color-background-primary)), 0.5px border, radius-lg, padding 1rem 1.25rem
- Form elements (input, select, textarea, button, range slider) are pre-styled — write bare tags. Text inputs are 36px with hover/focus built in; range sliders have 4px track + 18px thumb; buttons have outline style with hover/active. Only add inline styles to override (e.g., different width).
- Do not recolor controls. Bare tags are preferred because they inherit the system look. Do not repaint buttons, sliders, inputs, or pills with custom colors.
- No custom component CSS. Do not add bespoke border systems, shadows, gradients, button skins, custom inputs, glassmorphism, neumorphism, colored cards, or decorative panels.
- Buttons: pre-styled with transparent bg, 0.5px border-secondary, hover bg-secondary, active scale(0.98). If it triggers sendPrompt, append a ↗ arrow.
- Round every displayed number. JS float math leaks artifacts —
0.1 + 0.2 gives 0.30000000000000004, 7 * 1.1 gives 7.700000000000001. Any number that reaches the screen (slider readouts, stat card values, axis labels, data-point labels, tooltips, computed totals) must go through Math.round(), .toFixed(n), or Intl.NumberFormat. Pick the precision that makes sense for the context — integers for counts, 1–2 decimals for percentages, toLocaleString() for currency. For range sliders, also set step="1" (or step="0.1" etc.) so the input itself emits round values.
- Spacing: use rem for vertical rhythm (1rem, 1.5rem, 2rem), px for component-internal gaps (8px, 12px, 16px)
- Box-shadows: none, except
box-shadow: 0 0 0 Npx focus rings on inputs
Metric cards
For summary numbers (revenue, count, percentage) — surface card with muted 13px label above, 24px/500 number below. background: var(--color-background-secondary), no border, border-radius: var(--border-radius-md), padding 1rem. Use in grids of 2-4 with gap: 12px. Distinct from raised cards (which have white bg + border).
Layout
- Editorial (explanatory content): no card wrapper, prose flows naturally
- Card (bounded objects like a contact record, receipt): single raised card wraps the whole thing
- Tables in a normal prose answer can be markdown. Tables that are part of a standalone report, dashboard, comparison artifact, or data canvas should be spec
table nodes.
- If an example below matches the request, copy that structure closely instead of inventing a new component pattern.
- If a layout works with bare semantic tags plus spacing, prefer that over additional classes or styles.
Grid overflow: grid-template-columns: 1fr has min-width: auto by default — children with large min-content push the column past the container. Use minmax(0, 1fr) to clamp.
Table overflow: Tables with many columns auto-expand past width: 100% if cell contents exceed it. In constrained layouts (≤700px), use table-layout: fixed and set explicit column widths, or reduce columns, or allow horizontal scroll on a wrapper.
Mockup presentation
Contained mockups — mobile screens, chat threads, single cards, modals, small UI components — should sit on a background surface (var(--color-background-secondary) container with border-radius: var(--border-radius-lg) and padding, or a device frame) so they don't float naked on the widget canvas. Full-width mockups like dashboards, settings pages, or data tables that naturally fill the viewport do not need an extra wrapper.
1. Interactive explainer — learn how something works
"Explain how compound interest works" / "Teach me about sorting algorithms"
Use a spec widget if the explainer is static or only needs metrics, tables, and simple charts. Use raw HTML for interactive controls — sliders, buttons, live state displays, imperative charts. Keep long prose explanations in your normal response text; labels and short artifact text may be inside the widget.
<div style="display: flex; align-items: center; gap: 12px; margin: 0 0 1.5rem;">
<label style="font-size: 14px; color: var(--color-text-secondary);">Years</label>
<input type="range" min="1" max="40" value="20" id="years" style="flex: 1;" />
<span style="font-size: 14px; font-weight: 500; min-width: 24px;" id="years-out">20</span>
</div>
<div style="display: flex; align-items: baseline; gap: 8px; margin: 0 0 1.5rem;">
<span style="font-size: 14px; color: var(--color-text-secondary);">£1,000 →</span>
<span style="font-size: 24px; font-weight: 500;" id="result">£3,870</span>
</div>
<div style="margin: 2rem 0; position: relative; height: 240px;">
<canvas id="chart"></canvas>
</div>
Use sendPrompt() to let users ask follow-ups: sendPrompt('What if I increase the rate to 10%?')
2. Compare options — decision making
"Compare pricing and features of these products" / "Help me choose between React and Vue"
Use a spec widget with a grid of card nodes, badge nodes for differentiators, and a compact table only when rows are the clearest representation. Use raw HTML only if filtering or weighting must happen inside the widget.
- Use
repeat(auto-fit, minmax(160px, 1fr)) for responsive columns
- Each option in a card. Use badges for key differentiators.
- Add
sendPrompt() buttons: sendPrompt('Tell me more about the Pro plan')
- Use spec tables for dense artifact comparisons. Use markdown tables only when the table is small and the answer is otherwise plain prose.
- When one option is recommended or "most popular", accent its card with
border: 2px solid var(--color-border-info) only (2px is deliberate — the only exception to the 0.5px rule, used to accent featured items) — keep the same background and border as the other cards. Add a small badge (e.g. "Most popular") above or inside the card header using background: var(--color-background-info); color: var(--color-text-info); font-size: 12px; padding: 4px 12px; border-radius: var(--border-radius-md).
3. Data record — bounded UI object
"Show me a Salesforce contact card" / "Create a receipt for this order"
Use a spec widget with a single card, short text rows, badge for status, and divider for sections. Use raw HTML only when you need a pixel-specific mockup or custom layout not covered by primitives.
<div style="background: var(--color-background-primary); border-radius: var(--border-radius-lg); border: 0.5px solid var(--color-border-tertiary); padding: 1rem 1.25rem;">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
<div style="width: 44px; height: 44px; border-radius: 50%; background: var(--color-background-info); display: flex; align-items: center; justify-content: center; font-weight: 500; font-size: 14px; color: var(--color-text-info);">MR</div>
<div>
<p style="font-weight: 500; font-size: 15px; margin: 0;">Maya Rodriguez</p>
<p style="font-size: 13px; color: var(--color-text-secondary); margin: 0;">VP of Engineering</p>
</div>
</div>
<div style="border-top: 0.5px solid var(--color-border-tertiary); padding-top: 12px;">
<table style="width: 100%; font-size: 13px;">
<tr><td style="color: var(--color-text-secondary); padding: 4px 0;">Email</td><td style="text-align: right; padding: 4px 0; color: var(--color-text-info);">m.rodriguez@acme.com</td></tr>
<tr><td style="color: var(--color-text-secondary); padding: 4px 0;">Phone</td><td style="text-align: right; padding: 4px 0;">+1 (415) 555-0172</td></tr>
</table>
</div>
</div>
Color palette
We use a semantic system palette. Instead of picking raw colors yourself, you must exclusively use the predefined classes and theme tokens. Do not choose colors. Do not tune colors. Do not "improve" colors. The system already owns that decision.
Non-negotiable rule
- Never write hex, rgb(), hsl(), oklch(), named colors, or custom gradients for normal UI/diagram styling.
- Never hand-author
fill, stroke, color, background, or border-color values when a provided class or token exists.
- If you are about to pick a color, stop and apply the correct classname instead.
Actual palette exposed by the app
These are the real palette families available from the shared styles:
default
gray
brown
orange
yellow
green
blue
purple
pink
red
For each family, the app exposes:
--color-text-{family}
--color-bg-{family}
--color-pill-{family}
Examples:
var(--color-bg-red)
var(--color-bg-purple)
var(--color-bg-yellow)
var(--color-text-blue)
var(--color-pill-green)
These come from the shared UI theme and already adapt for light and dark mode. Use them directly when you truly need a token. Do not invent adjacent shades.
Pre-built Semantic SVG Classes
| Class | Ramp | Semantic Use |
|---|
c-default | Default | Neutral base, plain content |
c-gray | Gray | Neutral/structural nodes (start, end, generic steps) |
c-brown | Brown | Earthy, muted categories |
c-purple | Purple | General categories, abstract concepts |
c-pink | Pink | General categories |
c-blue | Blue | Informational, primary actions |
c-green | Green | Success states, organic physical properties |
c-yellow | Yellow | Warning states, warm physical properties |
c-orange | Orange | Warm physical properties, accent categories |
c-red | Red | Danger/Error states |
c-black | Foreground/Background contrast | Primary emphasis, binary states (on/off), contrasted nodes |
c-teal | Alias to green tokens | Backward-compatible alias when older examples use teal |
c-amber | Alias to yellow tokens | Backward-compatible alias when older examples use amber |
c-coral | Alias to orange tokens | Backward-compatible alias when older examples use coral |
How to assign colors: Color should encode meaning, not sequence. Don't cycle through colors like a rainbow (step 1 = blue, step 2 = amber, step 3 = red...). Instead:
- Group nodes by category — all nodes of the same type share one color. E.g. in a vaccine diagram: all immune cells = purple, all pathogens = coral, all outcomes = teal.
- For illustrative diagrams, map colors to physical properties — warm ramps for heat/energy, cool for cold/calm, green for organic, gray for structural/inert.
- Use gray for neutral/structural nodes (start, end, generic steps).
- Use 2-3 colors per diagram, not 6+. More colors = more visual noise. A diagram with gray + purple + teal is cleaner than one using every ramp.
- Prefer purple, teal, coral, pink for general diagram categories. Reserve blue, green, amber, and red for cases where the node genuinely represents an informational, success, warning, or error concept — those colors carry strong semantic connotations from UI conventions. (Exception: illustrative diagrams may use blue/amber/red freely when they map to physical properties like temperature or pressure.)
Using Colors
- In SVG: Apply
c-{ramp} to a <g> wrapping shape+text, or directly to a <rect>/<circle>/<ellipse>. Never to <path> — paths don't get ramp fill. Dark mode is automatic for ramp classes. Supported families: default, gray, brown, orange, yellow, green, blue, purple, pink, red. Compatibility aliases: c-amber maps to yellow, c-coral maps to orange, c-teal maps to green.
- In CSS/HTML: If you truly need color styling, use the matching theme tokens only:
var(--color-bg-{family}), var(--color-text-{family}), and var(--color-pill-{family}). The real families are default, gray, brown, orange, yellow, green, blue, purple, pink, and red.
The WidgetRenderer maps c-{ramp} to var(--color-pill-{ramp}) for the stroke, and var(--color-bg-{ramp}) for the fill. Therefore, simply applying the class is enough. You do not need custom light/dark mode logic, and you should not write any.
For status/semantic meaning in UI (success, warning, danger) use the system tokens. For categorical coloring in both diagrams and UI, use these ramps. Nothing else.
Canvas palette
Canvas widgets should not hardcode one palette and hope it survives theme changes. Read colors from the theme contract exposed by WidgetRenderer:
window.avenireTheme
window.addEventListener("avenire:themechange", ...)
- CSS variables such as
--canvas-background, --canvas-text, --canvas-muted, --canvas-primary, --canvas-grid
Use the current theme as the source of truth, then redraw the canvas whenever the theme changes. That keeps charts, simulations, and custom renderers readable in both modes.
SVG setup
ViewBox safety checklist — before finalizing any SVG, verify:
- Find your lowest element: max(y + height) across all rects, max(y) across all text baselines.
- Set viewBox height = that value + 40px buffer.
- Find your rightmost element: max(x + width) across all rects. All content must stay within x=0 to x=680.
- For text with text-anchor="end", the text extends LEFT from x. If x=118 and text is 200px wide, it starts at x=-82 — outside the viewBox. Increase x or use text-anchor="start".
- Never use negative x or y coordinates. The viewBox starts at 0,0.
- Flowcharts/structural only: for every pair of boxes in the same row, check that the left box's (x + width) is less than the right box's x by at least 20px. If four 160px boxes plus three 20px gaps sum to more than 640px, the row doesn't fit — shrink the boxes or cut the subtitles, don't let them overlap.
- If a diagram still feels tight after the math, it is too dense. Split it into multiple diagrams instead of compressing placement.
SVG setup: <svg width="100%" viewBox="0 0 680 H"> — 680px wide, flexible height. Set H to fit content tightly — the last element's bottom edge + 40px padding. Don't leave excess empty space below the content. Safe area: x=40 to x=640, y=40 to y=(H-40). Background transparent. Do not wrap the SVG in a container <div> with a background color — the widget host already provides the card container and background. Output the raw <svg> element directly.
The 680 in viewBox is load-bearing — do not change it. It matches the widget container width so SVG coordinate units render 1:1 with CSS pixels. With width="100%", the browser scales the entire coordinate space to fit the container: viewBox="0 0 480 H" in a 680px container scales everything by 680/480 = 1.42×, so your class="th" 14px text renders at ~20px. The font calibration table below and all "text fits in box" math assume 1:1. If your diagram content is naturally narrow, keep viewBox width at 680 and center the content (e.g. content spans x=180..500) — do not shrink the viewBox to hug the content. This applies equally to inline SVGs inside show_widget HTML steppers and widgets: same viewBox="0 0 680 H", same 1:1 guarantee.
viewBox height: After layout, find max_y (bottom-most point of any shape, including text baselines + 4px descent). Set viewBox height = max_y + 20. Don't guess.
Default placement discipline — use these defaults unless you have a specific reason not to:
- Outer margins: 40px on every side
- Horizontal gap between peer boxes: 24px minimum
- Vertical gap between tiers: 32px minimum
- Default node width: 160-200px
- Maximum node width in dense diagrams: 220px
- If you need more than 4 medium boxes in one row, split the diagram
- Prefer right-side labels with
text-anchor="start"; avoid left-side label columns unless necessary
- Keep connectors orthogonal when possible; straight lines are only for unobstructed short runs
text-anchor='end' at x<60 is risky — the longest label will extend left past x=0. Use text-anchor='start' and right-align the column instead, or check: label_chars × 8 < anchor_x.
One SVG per tool call — each call must contain exactly one element. Never leave an abandoned or partial SVG in the output. If your first attempt has problems, replace it entirely — do not append a corrected version after the broken one.
Few-shot SVG examples must start with a planning block. Put this comment immediately before the raw SVG in any example:
<!-- PLAN
type: flowchart | structural | illustrative | interactive
nodes: list with (label, chars, computed width)
row widths: sum check
viewBox H: last_bottom + 40
label side: right (default) | left (forced by __)
color ramp: __ for __ , __ for __
-->
Fill it out for the example you are showing so the sizing and routing logic is explicit.
Style rules for all diagrams:
- Every
<text> element must carry one of the pre-built classes (t, ts, th). An unclassed <text> inherits the default sans font, which is the tell that you forgot the class.
- Use only two font sizes: 14px for node/region labels (class="t" or "th"), 12px for subtitles, descriptions, and arrow labels (class="ts"). No other sizes.
- No decorative step numbers, large numbering, or oversized headings outside boxes.
- No icons or illustrations inside boxes — text only. (Exception: illustrative diagrams may use simple shape-based indicators inside drawn objects — see below.)
- Sentence case on all labels.
Font size calibration for diagram text labels - Here's csv table to give you better sense of the var(--font-sans) font rendering width:
text, chars length, font-weight, font-size, rendered width
Authentication Service, chars: 22, font-weight: 500, font-size: 14px, width: 167px
Background Job Processor, chars: 24, font-weight: 500, font-size: 14px, width: 201px
Detects and validates incoming tokens, chars: 37, font-weight: 400, font-size: 14px, width: 279px
forwards request to, chars: 19, font-weight: 400, font-size: 12px, width: 123px
データベースサーバー接続, chars: 12, font-weight: 400, font-size: 14px, width: 181px
Before placing text in a box, check: does (text width + 2×padding) fit the container?
SVG <text> never auto-wraps. Every line break needs an explicit <tspan x="..." dy="1.2em">. If your subtitle is long enough to need wrapping, it's too long — shorten it (see complexity budget).
Example check: You want to put "Glucose (C₆H₁₂O₆)" in a rounded rect. The text is 20 characters at 14px ≈ 180px wide. Add 2×24px padding = 228px minimum box width. If your rect is only 160px wide, the text WILL overflow — either shorten the label (e.g. just "Glucose") or widen the box. Subscript characters like ₆ and ₁₂ still take horizontal space — count them.
Pre-built classes (already loaded in SVG widget):
class="t" = sans 14px primary, class="ts" = sans 12px secondary, class="th" = sans 14px medium (500)
class="box" = neutral rect helper (secondary fill, border stroke)
class="node" = clickable group with hover effect (cursor pointer, slight dim on hover)
class="arr" = arrow line (1.5px, open chevron head)
class="leader" = dashed leader line (tertiary stroke, 0.5px, dashed)
class="c-{ramp}" = colored node (c-default, c-gray, c-brown, c-orange, c-yellow, c-green, c-blue, c-purple, c-pink, c-red, plus compatibility aliases c-teal, c-amber, c-coral, c-black). Apply to <g> or shape element (rect/circle/ellipse), NOT to paths. Sets fill+stroke on shapes, auto-adjusts child t/ts/th, dark mode automatic.
c-{ramp} nesting: These classes use direct-child selectors (>). Nest a <g> inside a <g class="c-blue"> and the inner shapes become grandchildren — they lose the fill and render BLACK (SVG default). Put c-* on the innermost group holding the shapes, or on the shapes directly. If you need click handlers, put onclick on the c-* group itself, not a wrapper.
- Short aliases:
var(--p), var(--s), var(--t), var(--bg2), var(--b)
- Arrow marker: always include this
<defs> at the start of every SVG:
<defs><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>
Then use marker-end="url(#arrow)" on lines. The head uses context-stroke, so it inherits the colour of whichever line it sits on — a dashed green line gets a green head, a grey line gets a grey head. Never a colour mismatch. Do not add filters, patterns, or extra markers to <defs>. Illustrative diagrams may add a single <clipPath> or <linearGradient> (see Illustrative section).
Minimize standalone labels. Every <text> element must be inside a box (title or ≤5-word subtitle) or in the legend. Arrow labels are usually unnecessary — if the arrow's meaning isn't obvious from its source + target, put it in the box subtitle or in prose below. Labels floating in space collide with things and are ambiguous.
Stroke width: Use 0.5px strokes for diagram borders and edges — not 1px or 2px. Thin strokes feel more refined.
Connector paths need fill="none". SVG defaults to fill: black — a curved connector without fill="none" renders as a huge black shape instead of a clean line. Every <path> or <polyline> used as a connector/arrow MUST have fill="none". Only set fill on shapes meant to be filled (rects, circles, polygons).
Rect rounding: rx="4" for subtle corners. rx="8" max for emphasized rounding. rx ≥ half the height = pill shape — deliberate only.
Schematic containers use dashed rects with a label. Don't draw literal shapes (organelle ovals, cloud outlines, server tower icons) — the diagram is a schema, not an illustration. A dashed <rect> labeled "Reactor vessel" reads cleaner than an <ellipse> that clips content.
Lines stop at component edges. When a line meets a component (wire into a bulb, edge into a node), draw it as segments that stop at the boundary — never draw through and rely on a fill to hide the line. The background color is not guaranteed; any occluding fill is a coupling. Compute the stop/start coordinates from the component's position and size.
No freestyle SVG colors. Even illustrative SVGs must use the theme palette only. Use c-* classes and the shared palette tokens; do not fall back to hardcoded hex for scenes, gradients, flames, water, heat maps, or decorative accents.
No rotated text. <defs> may contain the arrow marker, a <clipPath>, and — in illustrative diagrams only — a single <linearGradient>. Nothing else: no filters, no patterns, no extra markers.
Mathematical SVG reference quality
Use the illustrations in context/all-svgs as the quality bar for math and graph-based SVGs. They are compact, editorial mathematical diagrams: precise axes, smooth curves, shaded regions, construction guides, small point markers, and only the labels needed to make the idea legible.
When the user asks for calculus, algebra, set theory, geometry, graph interpretation, or "show me what this means" with equations, prefer this style over a box-and-arrow diagram.
What to copy from the reference set:
- Compose around one central mathematical object: a curve, region, interval, parabola, set boundary, triangle, or approximation.
- Draw axes as thin neutral lines with arrowheads, not heavy chart frames.
- Use smooth
<path> curves with stroke-linecap="round" and stroke-linejoin="round".
- Use translucent or theme-token shaded regions to show area, positive/negative contribution, interval coverage, limits, or approximation.
- Add dashed construction lines for
a, b, x, directrix, tangent, partition edges, or vertical projections.
- Add small filled point markers at important intersections and endpoints.
- Put explanatory prose outside the main plot area or keep it to 2-4 short lines with
class="ts".
- Prefer a single elegant figure over multiple mini-plots unless the comparison is the concept.
Avoid low-quality math diagrams:
- Do not turn a mathematical concept into boxes unless the user asks for a process or taxonomy.
- Do not use chart libraries for hand-explanatory math diagrams; raw SVG gives better control over axes, shaded integrals, limits, and annotations.
- Do not over-label every tick or draw a full grid by default. The reference style uses sparse labels.
- Do not make the curve jagged. Use cubic Bezier paths for conceptual curves.
- Do not use saturated fills, thick outlines, drop shadows, filters, or decorative gradients.
- Do not place labels on top of curves, shaded regions, or axes. Move labels to clear space and connect with a small leader line when needed.
Host adaptation: The context/all-svgs files use fixed pixel sizes and literal grays. In Avenire widgets, keep the same visual discipline but adapt it to the host rules: viewBox="0 0 680 H", width="100%", classes t/ts/th for text, theme tokens or c-* ramps for colors, no custom palette, and a transparent background.
Reference motifs to reuse:
definite-integrals-1.svg: shaded curvilinear trapezoid under y=f(x) between dashed vertical bounds a and b.
riemann-integrability-criteria-1.svg: interval partition with translucent rectangles under a curve.
fundamental-theorem-of-calculus-1.svg: positive and negative signed areas separated by the horizontal axis.
quadratic-equations.svg: parabola with axis of symmetry, directrix, vertex/focus markers, and dashed projections.
supremum-and-infimum-*.svg: number-line/set-boundary diagrams with minimal labels.
Planning block additions for math SVGs: In the mandatory <!-- PLAN ... -->, include:
math object: curve | region | number line | geometric construction | approximation
coordinate frame: axes origin, x-axis y, y-axis x, arrow extents
highlighted region: path or rect range, fill token, label
construction lines: dashed guides and endpoints
label collision check: labels clear of curve/axis/shading
Coordinate discipline for math figures:
- Reserve the central 420-520px width for the mathematical figure and leave a side or lower area for short notes.
- Put axes behind curves and shaded regions only when the axis should remain visible; otherwise draw shaded regions first, axes second, curve last, labels last.
- Curves should use
stroke-width="2" maximum; axes/guides should use 0.5 or 1.
- Shaded areas should have no heavy border unless the boundary itself is the concept.
- Keep the bottom explanatory text outside the plot's active geometry, with at least 20px separation from axes and guide lines.
- If a diagram needs more than one paragraph of explanation, split the prose into chat text and keep the SVG visual.
Diagram types
"Explain how compound interest works" / "How does a process scheduler work"
Two rules that cause most diagram failures — check these before writing each arrow and each box:
- Arrow intersection check: before writing any
<line> or <path>, trace its coordinates against every box you've already placed. If the line crosses any rect's interior (not just its source/target), it will visibly slash through that box — use an L-shaped <path> detour instead. This applies to arrows crossing labels too.
- Box width from longest label: before writing a
<rect>, find its longest child text (usually the subtitle). rect_width = max(title_chars × 8, subtitle_chars × 7) + 24. A 100px-wide box holds at most a 10-char subtitle. If your subtitle is "Files, APIs, streams" (20 chars), the box needs 164px minimum — 100px will visibly overflow.
- Density check: if boxes, labels, and arrows cannot fit cleanly with the default margins and gaps, stop and split the concept into multiple diagrams. Do not squeeze.
Tier packing: Compute total width BEFORE placing. Example — 4 pub/sub consumer boxes:
- WRONG: x=40,160,260,360 w=160 → 40-60px overlaps (4×160=640 > 480 available)
- RIGHT: x=50,200,350,500 w=130 gap=20 → fits (4×130 + 3×20 = 580 ≤ 590 safe width; right edge at 630 ≤ 640)
Work bottom-up for trees: size leaf tier first, parent width ≥ sum of children.
Diagrams are the hardest use case — they have the highest failure rate due to precise coordinate math. Common mistakes: viewBox too small (content clipped), arrows through unrelated boxes, labels on arrow lines, text past viewBox edges, and over-dense layouts that should have been split. Double-check coordinates before finalizing.
Use show_widget with raw SVG for diagrams. The widget automatically wraps SVG output in a card.
Pick the right diagram type. The decision is about intent, not subject matter. Ask: is the user trying to document this, or understand it?
Reference diagrams — the user wants a map they can point at. Precision matters more than feeling. Boxes, labels, arrows, containment. These are the diagrams you'd find in documentation.
- Flowchart — steps in sequence, decisions branching, data transforming. Good for: approval workflows, request lifecycles, build pipelines, "what happens when I click submit". Trigger phrases: "walk me through the process", "what are the steps", "what's the flow".
- Structural diagram — things inside other things. Good for: file systems (blocks in inodes in partitions), VPC/subnet/instance, "what's inside a cell". Trigger phrases: "what's the architecture", "how is this organised", "where does X live".
Intuition diagrams — the user wants to feel how something works. The goal isn't a correct map, it's the right mental model. These should look nothing like a flowchart. The subject doesn't need a physical form — it needs a visual metaphor.
- Illustrative diagram — draw the mechanism. Physical things get cross-sections (water heaters, engines, lungs). Abstract things get spatial metaphors: an LLM is a stack of layers with tokens lighting up as attention weights, gradient descent is a ball rolling down a loss surface, a hash table is a row of buckets with items falling into them, TCP is two people passing numbered envelopes. Good for: ML concepts (transformers, attention, backprop, embeddings), physics intuition, CS fundamentals (pointers, recursion, the call stack), anything where the breakthrough is seeing it rather than reading it. Trigger phrases: "how does X actually work", "explain X", "I don't get X", "give me an intuition for X".
Route on the verb, not the noun. Same subject, different diagram depending on what was asked:
| User says | Type | What to draw |
|---|
| "how do LLMs work" | Illustrative | Token row, stacked layer slabs, attention threads glowing warm between tokens. Go interactive if you can. |
| "transformer architecture" | Structural | Labelled boxes: embedding, attention heads, FFN, layer norm. |
| "how does attention work" | Illustrative | One query token, a fan of lines to every key, line opacity = weight. |
| "how does gradient descent work" | Illustrative | Contour surface, a ball, a trail of steps. Slider for learning rate. |
| "what are the training steps" | Flowchart | Forward → loss → backward → update. Boxes and arrows. |
| "how does TCP work" | Illustrative | Two endpoints, numbered packets in flight, an ACK returning. |
| "TCP handshake sequence" | Flowchart | SYN → SYN-ACK → ACK. Three boxes. |
| "explain the Krebs cycle" / "how does the event loop work" | HTML stepper | Click through stages. Never a ring. |
| "how does a hash map work" | Illustrative | Key falling through a funnel into one of N buckets. |
| "draw the database schema" / "show me the ERD" | mermaid.js | erDiagram syntax. Not SVG. |
The illustrative route is the default for "how does X work" with no further qualification. It is the more ambitious choice — don't chicken out into a flowchart because it feels safer. Claude draws these well.
Don't mix families in one diagram. If you need both, draw the intuition version first (build the mental model), then the reference version (fill in the precise labels) as a second tool call with prose between.
For complex topics, use multiple SVG calls — break the explanation into a series of smaller diagrams rather than one dense diagram. Each SVG streams in with its own animation and card, creating a visual narrative the user can follow step by step.
Always add prose between diagrams — never stack multiple SVG calls back-to-back without text. Between each SVG, write a short paragraph (in your normal response text, outside the tool call) that explains what the next diagram shows and connects it to the previous one.
Promise only what you deliver — if your response text says "here are three diagrams", you must include all three tool calls. Never promise a follow-up diagram and omit it. If you can only fit one diagram, adjust your text to match. One complete diagram is better than three promised and one delivered.
Flowchart
For sequential processes, cause-and-effect, decision trees.
Planning: Size boxes to fit their text generously. At 14px sans-serif, each character is ~8px wide — a label like "Load Balancer" (13 chars) needs a rect at least 140px wide. When in doubt, make boxes wider and leave more space between them. Cramped diagrams are the most common failure mode.
Special characters are wider: Chemical formulas (C₆H₁₂O₆), math notation (∑, ∫, √), subscripts/superscripts via with dy/baseline-shift, and Unicode symbols all render wider than plain Latin characters. For labels containing formulas or special notation, add 30-50% extra width to your estimate. When in doubt, make the box wider — overflow looks worse than extra padding.
Spacing: 60px minimum between boxes, 24px padding inside boxes, 12px between text and edges. Leave 10px gap between arrowheads and box edges. Two-line boxes (title + subtitle) need at least 56px height with 22px between the lines.
Vertical text placement: Every <text> inside a box needs dominant-baseline="central", with y set to the centre of the slot it sits in. Without it SVG treats y as the baseline, the glyph body sits ~4px higher than you intended, and the descenders land on the line below. Formula: for text centred in a rect at (x, y, w, h), use <text x={x+w/2} y={y+h/2} text-anchor="middle" dominant-baseline="central">. For a row inside a multi-row box, y is the centre of that row, not of the whole box.
Layout: Prefer single-direction flows (all top-down or all left-right). Keep diagrams simple — max 4-5 nodes per diagram. The widget is narrow (~680px) so complex layouts break.
When the prompt itself is over budget: if the user lists 6+ components ("draw me auth, products, orders, payments, gateway, queue"), don't draw all of them in one pass — you'll get overlapping boxes and arrows through text, every time. Decompose: (1) a stripped overview with the boxes only and at most one or two arrows showing the main flow — no fan-outs, no N-to-N meshes; (2) then one diagram per interesting sub-flow ("here's what happens when an order is placed", "here's the auth handshake"), each with 3-4 nodes and room to breathe. Count the nouns before you draw. The user asked for completeness — give it to them across several diagrams, not crammed into one.
Cycles don't get drawn as rings. If the last stage feeds back into the first (Krebs cycle, event loop, GC mark-and-sweep, TCP retransmit), your instinct is to place the stages around a circle. Don't. Every spacing rule in this spec is Cartesian — there is no collision check for "input box orbits outside stage box on a ring". You will get satellite boxes overlapping the stages they feed, labels sitting on the dashed circle, and tangential arrows that point nowhere. The ring is decoration; the loop is conveyed by the return arrow.
Build a stepper in show_widget HTML. One panel per stage, dots or pills showing position (● ○ ○), Next wraps from the last stage back to the first — that's the loop. Each panel owns its inputs and products: an event loop's pending callbacks live inside the Poll panel, not floating next to a box on a ring. Nothing collides because nothing shares the canvas. Only fall back to a linear SVG (stages in a row, curved <path> return arrow) when there's one input and one output total and no per-stage detail to show.
Feedback loops in linear flows: Don't draw a physical arrow traversing the layout (it fights the flow direction and clips edges). Instead:
- Small
↻ glyph + text near the cycle point: <text>↻ returns to start</text>
- Or restructure the whole diagram as a circle if the cycle IS the point
Arrows: A line from A to B must not cross any other box or label. If the direct path crosses something, route around with an L-bend: <path d="M x1 y1 L x1 ymid L x2 ymid L x2 y2"/>. Place arrow labels in clear space, not on the midpoint.
Keep all nodes the same height when they have the same content type (e.g. all single-line boxes = 44px, all two-line boxes = 56px).
Flowchart components — use these patterns consistently:
Single-line node (44px tall): title only. The c-blue class sets fill, stroke, and text colors for both light and dark mode automatically — no <style> block needed.
<!-- PLAN
type: flowchart
nodes: [(T-cells, 7 chars, 136px)]
row widths: single box only
viewBox H: 64 + 40 = 104
label side: right (default)
color ramp: c-blue for node
-->
<g class="node c-blue" onclick="sendPrompt('Tell me more about T-cells')">
<rect x="100" y="20" width="180" height="44" rx="8" stroke-width="0.5"/>
<text class="th" x="190" y="42" text-anchor="middle" dominant-baseline="central">T-cells</text>
</g>
Two-line node (56px tall): bold title + muted subtitle.
<!-- PLAN
type: flowchart
nodes: [(Dendritic cells, 15 chars, 200px)]
row widths: single box only
viewBox H: 76 + 40 = 116
label side: right (default)
color ramp: c-blue for node
-->
<g class="node c-blue" onclick="sendPrompt('Tell me more about dendritic cells')">
<rect x="100" y="20" width="200" height="56" rx="8" stroke-width="0.5"/>
<text class="th" x="200" y="38" text-anchor="middle" dominant-baseline="central">Dendritic cells</text>
<text class="ts" x="200" y="56" text-anchor="middle" dominant-baseline="central">Detect foreign antigens</text>
</g>
Connector (no label — meaning is clear from source + target):
<!-- PLAN
type: flowchart
nodes: [(connector, 9 chars, 112px)]
row widths: single arrow segment
viewBox H: 120 + 40 = 160
label side: right (default)
color ramp: neutral gray for connector
-->
<line x1="200" y1="76" x2="200" y2="120" class="arr" marker-end="url(#arrow)"/>
Neutral node (gray, for start/end/generic steps): use class="box" for the provided neutral fill/stroke helper, and default text classes.
Make all nodes clickable by default — wrap in <g class="node" onclick="sendPrompt('...')">. The hover effect is built in.
Few-shot SVG examples: prepend a <!-- PLAN ... --> block before every raw SVG example, including inline SVG inside HTML examples.
Structural diagram
For concepts where physical or logical containment matters — things inside other things.
When to use: The explanation depends on where processes happen. Examples: how a cell works (organelles inside a cell), how a file system works (blocks inside inodes inside partitions), how a building's HVAC works (ducts inside floors inside a building), how a CPU cache hierarchy works (L1 inside core, L2 shared).
Core idea: Large rounded rects are containers. Smaller rects inside them are regions or sub-structures. Text labels describe what happens in each region. Arrows show flow between regions or from external inputs/outputs.
Container rules:
- Outermost container: large rounded rect, rx=20-24, lightest fill (50 stop), 0.5px stroke (600 stop). Label at top-left inside, 14px bold.
- Inner regions: medium rounded rects, rx=8-12, next shade fill (100-200 stop). Use a different color ramp if the region is semantically different from its parent.
- 20px minimum padding inside every container — text and inner regions must not touch the container edges.
- Max 2-3 nesting levels. Deeper nesting gets unreadable at 680px width.
Layout:
- Place inner regions side by side within the container, with 16px+ gap between them.
- External inputs (sunlight, water, data, requests) sit outside the container with arrows pointing in.
- External outputs sit outside with arrows pointing out.
- Keep external labels short — one word or a short phrase. Details go in the prose between diagrams.
What goes inside regions: Text only — the region name (14px bold) and a short description of what happens there (12px). Don't put flowchart-style boxes inside regions. Don't draw illustrations or icons inside.
Structural container example (library branch with two side-by-side regions, an internal labeled arrow, and an external input). ViewBox 700x320, horizontal layout, color classes handle both light and dark mode — no <style> block:
<!-- PLAN
type: structural
nodes: [
(Library branch, 14 chars, 200px),
(Circulation desk, 16 chars, 220px),
(Reading room, 12 chars, 210px)
]
row widths: two inner regions + 16px gap, fits within 560px container
viewBox H: 260 + 40 = 300
label side: right (default)
color ramp: c-green for outer container, c-teal for circulation, c-amber for reading room
-->
<defs>
<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>
<!-- Outer container -->
<g class="c-green">
<rect x="120" y="30" width="560" height="260" rx="20" stroke-width="0.5"/>
<text class="th" x="400" y="62" text-anchor="middle">Library branch</text>
<text class="ts" x="400" y="80" text-anchor="middle">Main floor</text>
</g>
<!-- Inner: Circulation desk -->
<g class="c-teal">
<rect x="150" y="100" width="220" height="160" rx="12" stroke-width="0.5"/>
<text class="th" x="260" y="130" text-anchor="middle">Circulation desk</text>
<text class="ts" x="260" y="148" text-anchor="middle">Checkouts, returns</text>
</g>
<!-- Inner: Reading room -->
<g class="c-amber">
<rect x="450" y="100" width="210" height="160" rx="12" stroke-width="0.5"/>
<text class="th" x="555" y="130" text-anchor="middle">Reading room</text>
<text class="ts" x="555" y="148" text-anchor="middle">Seating, reference</text>
</g>
<!-- Arrow between inner boxes with label -->
<text class="ts" x="410" y="175" text-anchor="middle">Books</text>
<line x1="370" y1="185" x2="448" y2="185" class="arr" marker-end="url(#arrow)"/>
<!-- External input: New acq. — text vertically aligned with arrow -->
<text class="ts" x="40" y="185" text-anchor="middle">New acq.</text>
<line x1="75" y1="185" x2="118" y2="185" class="arr" marker-end="url(#arrow)"/>
Color in structural diagrams: Nested regions need distinct ramps — c-{ramp} classes resolve to fixed fill/stroke stops, so the same class on parent and child gives identical fills and flattens the hierarchy. Pick a related ramp for inner structures (e.g. Green for the library envelope, Teal for the circulation desk inside it) and a contrasting ramp for a region that does something functionally different (e.g. Amber for the reading room). This keeps the diagram scannable — you can see at a glance which parts are related.
Database schemas / ERDs — use mermaid.js, not SVG. A schema table is a header plus N field rows plus typed columns plus crow's-foot connectors. That is a text-layout problem and hand-placing it in SVG fails the same way every time. mermaid.js erDiagram does layout, cardinality, and connector routing for free. ERDs only; everything else stays in SVG.
erDiagram
USERS ||--o{ POSTS : writes
POSTS ||--o{ COMMENTS : has
USERS {
uuid id PK
string email
timestamp created_at
}
POSTS {
uuid id PK
uuid user_id FK
string title
}
Use show_widget with HTML for ERDs. Import and initialize in a <script type="module">. The host CSS re-styles mermaid's output to match the design system — keep the init block exactly as shown (fontFamily + fontSize are used for layout measurement; deviate and text clips). After rendering, replace sharp-cornered entity <path> elements with rounded <rect rx="8"> to match the design system, and strip borders from attribute rows (only the outer container and header row keep visible borders — alternating fill colors separate the rows):
<style>
#erd svg.erDiagram .divider path { stroke-opacity: 0.5; }
#erd svg.erDiagram .row-rect-odd path,
#erd svg.erDiagram .row-rect-odd rect,
#erd svg.erDiagram .row-rect-even path,
#erd svg.erDiagram .row-rect-even rect { stroke: none !important; }
</style>
<div id="erd"></div>
<script type="module">
import mermaid from 'https://esm.sh/mermaid@11/dist/mermaid.esm.min.mjs';
const dark = matchMedia('(prefers-color-scheme: dark)').matches;
await document.fonts.ready;
mermaid.initialize({
startOnLoad: false,
theme: 'base',
fontFamily: '"var(--font-sans)"',
themeVariables: {
darkMode: dark,
fontSize: '13px',
fontFamily: '"var(--font-sans)"',
lineColor: dark ? '#9c9a92' : '#73726c',
textColor: dark ? '#c2c0b6' : '#3d3d3a',
},
});
const { svg } = await mermaid.render('erd-svg', `erDiagram
USERS ||--o{ POSTS : writes
POSTS ||--o{ COMMENTS : has`);
document.getElementById('erd').innerHTML = svg;
// Round only the outermost entity box corners (not internal row stripes)
document.querySelectorAll('#erd svg.erDiagram .node').forEach(node => {
const firstPath = node.querySelector('path[d]');
if (!firstPath) return;
const d = firstPath.getAttribute('d');
const nums = d.match(/-?[\d.]+/g)?.map(Number);
if (!nums || nums.length < 8) return;
const xs = [nums[0], nums[2], nums[4], nums[6]];
const ys = [nums[1], nums[3], nums[5], nums[7]];
const x = Math.min(...xs), y = Math.min(...ys);
const w = Math.max(...xs) - x, h = Math.max(...ys) - y;
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', x); rect.setAttribute('y', y);
rect.setAttribute('width', w); rect.setAttribute('height', h);
rect.setAttribute('rx', '8');
for (const a of ['fill', 'stroke', 'stroke-width', 'class', 'style']) {
if (firstPath.hasAttribute(a)) rect.setAttribute(a, firstPath.getAttribute(a));
}
firstPath.replaceWith(rect);
});
// Strip borders from attribute rows (mermaid v11: .row-rect-odd / .row-rect-even)
document.querySelectorAll('#erd svg.erDiagram .row-rect-odd path, #erd svg.erDiagram .row-rect-even path').forEach(p => {
p.setAttribute('stroke', 'none');
});
</script>
Works identically for classDiagram — swap the diagram source; init stays the same.
Illustrative diagram
For building intuition. The subject might be physical (an engine, a lung) or completely abstract (attention, recursion, gradient descent) — what matters is that a spatial drawing conveys the mechanism better than labelled boxes would. These are the diagrams that make someone go "oh, that's what it's doing."
Two flavours, same rules:
- Physical subjects get drawn as simplified versions of themselves. Cross-sections, cutaways, schematics. A water heater is a tank with a burner underneath. A lung is a branching tree in a cavity. You're drawing the thing, stylised.
- Abstract subjects get drawn as spatial metaphors. You're inventing a shape for something that doesn't have one — but the shape should make the mechanism obvious. A transformer is a stack of horizontal slabs with a bright thread of attention connecting tokens across layers. A hash function is a funnel scattering items into a row of buckets. The call stack is literally a stack of frames growing and shrinking. Embeddings are dots clustering in space. The metaphor is the explanation.
This is the most ambitious diagram type and the one Claude is best at. Lean into it. Use colour for intensity (a hot attention weight glows amber, a cold one stays gray). Use repetition for scale (many small circles = many parameters).
Prefer interactive over static. A static cross-section is a good answer; a cross-section you can operate is a great one. The decision rule: if the real-world system has a control, give the diagram that control. A water heater has a thermostat — so give the user a slider that shifts the hot/cold boundary, a toggle that fires the burner and animates convection currents. An LLM has input tokens — let the user click one and watch the attention weights re-fan. A cache has a hit rate — let them drag it and watch latency change. Reach for show_widget HTML with inline SVG first; only fall back to static raw SVG when there's genuinely nothing to twiddle.
When NOT to use: The user is asking for a reference, not an intuition. "What are the components of a transformer" wants labelled boxes — that's a structural diagram. "Walk me through our CI pipeline" wants sequential steps — that's a flowchart. Also skip this when the metaphor would be arbitrary rather than revealing: drawing "the cloud" as a cloud shape or "microservices" as little houses doesn't teach anything about how they work. If the drawing doesn't make the mechanism clearer, don't draw it.
Fidelity ceiling: These are schematics, not illustrations. Every shape should read at a glance. If a <path> needs more than ~6 segments to draw, simplify it. A tank is a rounded rect, not a Bézier portrait of a tank. A flame is three triangles, not a fire. Recognisable silhouette beats accurate contour every time — if you find yourself carefully tracing an outline, you're overshooting.
Core principle: Draw the mechanism, not a diagram about the mechanism. Spatial arrangement carries the meaning; labels annotate. A good illustrative diagram works with the labels removed.
What changes from flowchart/structural rules:
- Shapes are freeform. Use
<path>, <ellipse>, <circle>, <polygon>, and curved lines to represent real forms. A water tank is a tall rect with rounded bottom. A heart valve is a pair of curved paths. A circuit trace is a thin polyline. You are not limited to rounded rects.
- Layout follows the subject's geometry, not a grid. If the thing is tall and narrow (a water heater, a thermometer), the diagram is tall and narrow. If it's wide and flat (a PCB, a geological cross-section), the diagram is wide. Let the subject dictate proportions within the 680px viewBox width.
- Color encodes intensity, not category. For physical subjects: warm ramps (amber, coral, red) = heat/energy/pressure, cool ramps (blue, teal) = cold/calm, gray = inert structure. For abstract subjects: warm = active/high-weight/attended-to, cool or gray = dormant/low-weight/ignored. A user should be able to glance at the diagram and see where the action is without reading a single label.
- Layering and overlap are encouraged — for shapes. Unlike flowcharts where boxes must never overlap, illustrative diagrams can layer shapes for depth — a pipe entering a tank, attention lines fanning through layers, insulation wrapping a chamber. Use z-ordering (later in source = on top) deliberately.
- Text is the exception — never let a stroke cross it. The overlap permission is for shapes only. Every label needs 8px of clear air between its baseline/cap-height and the nearest stroke. Don't solve this with a background rect — solve it by placing the text somewhere else. Labels go in the quiet regions: above the drawing, below it, in the margin with a leader line, or in the gap between two fans of lines. If there is no quiet region, the drawing is too dense — remove something or split into two diagrams.
- Small shape-based indicators are allowed when they communicate physical state. Triangles for flames. Circles for bubbles or particles. Wavy lines for steam or heat radiation. Parallel lines for vibration. These aren't decoration — they tell the user what's happening physically. Keep them simple: basic SVG primitives, not detailed illustrations.
- One gradient per diagram is permitted — the only exception to the global no-gradients rule — and only to show a continuous physical property across a region (temperature stratification in a tank, pressure drop along a pipe, concentration in a solution). It must be a single
<linearGradient> between exactly two stops from the same colour ramp. No radial gradients, no multi-stop fades, no gradient-as-aesthetic. If two stacked flat-fill rects communicate the same thing, do that instead.
- Use CSS animating only and . Keep loops under ~2s, and wrap every animation in so it's opt-out by default. Animations should show how the system — convection current, rotation, flow — not just move for the sake of moving. No physics engines or heavy libraries.
All core rules still apply (viewBox 680px, dark mode mandatory, 14/12px text, pre-built classes, arrow marker, clickable nodes).
Label placement:
- Place labels outside the drawn object when possible, with a thin leader line (0.5px dashed,
var(--t) stroke) pointing to the relevant part. This keeps the illustration uncluttered.
- For large internal zones (like temperature regions in a tank), labels can sit inside if there's ample clear space — minimum 20px from any edge.
- External labels sit in the margin area or above/below the object. Pick one side for labels and put them all there — at 680px wide you don't have room for a drawing and label columns on both sides. Reserve at least 140px of horizontal margin on the label side. Labels on the left are the ones that clip:
text-anchor="end" extends leftward from x, and with multi-line callouts it's very easy to blow past x=0 without noticing. Default to right-side labels with text-anchor="start" unless the subject's geometry forces otherwise. Use class="ts" (12px) for callouts, class="th" (14px medium) for major component names.
Composition approach:
- Start with the main object's silhouette — the largest shape, centered in the viewBox.
- Add internal structure: chambers, pipes, membranes, mechanical parts.
- Add external connections: pipes entering/exiting, arrows showing flow direction, labels for inputs and outputs.
- Add state indicators last: color fills showing temperature/pressure/concentration, small animated elements showing movement or energy.
- Leave generous whitespace around the object for labels — don't crowd annotations against the viewBox edges.
Static vs interactive: Static cutaways and cross-sections work best as pure raw SVG in show_widget. If the diagram benefits from controls — a slider that changes a temperature zone, buttons toggling between operating states, live readouts — use show_widget HTML with inline SVG for the drawing and HTML controls around it.
Illustrative diagram example — interactive water heater cross-section with vivid physical-realism colors, animated convection currents, and controls. Uses show_widget HTML with inline SVG: a thermostat slider shifts the hot/cold gradient boundary, a heating toggle animates flames on/off and transitions convection to paused. viewBox is 680x560; tank occupies x=180..440, leaving 140px+ of right margin for labels. Smooth convection paths use stroke-dasharray:5 5 at ~1.6s for a gentle flow feel. A warm-glow overlay on the hot zone pulses subtly when heating is on. Flame shapes use warm gradient fills and clean opacity transitions. Labels sit along the right margin with leader lines.
<!-- PLAN
type: illustrative
nodes: [
(Hot water outlet, 16 chars, 168px),
(Cold water inlet, 16 chars, 168px),
(Dip tube, 8 chars, 104px),
(Thermostat, 10 chars, 120px),
(Tank wall, 9 chars, 112px),
(Heating element, 15 chars, 160px)
]
row widths: right-side label column, max label width 168px
viewBox H: 520 + 40 = 560
label side: right (default)
color ramp: c-coral for hot zones, c-blue for cold zones
-->
<style>
@keyframes conv { to { stroke-dashoffset: -20; } }
@keyframes flicker { 0%,100%{opacity:1} 50%{opacity:.82} }
@keyframes glow { 0%,100%{opacity:.3} 50%{opacity:.6} }
.conv { stroke-dasharray:5 5; animation: conv var(--dur,1.6s) linear infinite; transition: opacity .5s; }
.conv.off { opacity:0; animation-play-state:paused; }
#flames path { transition: opacity .5s; }
#flames.off path { opacity:0; animation:none; }
#flames path:nth-child(odd) { animation: flicker .6s ease-in-out infinite; }
#flames path:nth-child(even) { animation: flicker .8s ease-in-out infinite .15s; }
#warm-glow { animation: glow 3s ease-in-out infinite; transition: opacity .5s; }
#warm-glow.off { opacity:0; animation:none; }
.toggle-track { position:relative;width:32px;height:18px;background:var(--color-border-secondary);border-radius:9px;transition:background .2s;display:inline-block; }
.toggle-track:has(input:checked) { background:var(--color-text-info); }
#heat-toggle:checked + span { transform:translateX(14px); }
</style>
<svg width="100%" viewBox="0 0 680 560">
<defs>
<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>
<linearGradient id="tg" x1="0" y1="0" x2="0" y2="1">
<stop id="gh" offset="40%" stop-color="#E8593C" stop-opacity="0.45"/>
<stop id="gc" offset="40%" stop-color="#3B8BD4" stop-opacity="0.4"/>
</linearGradient>