Isometric SVG diagrams for technical docs with Claude Code
Originally published on Homingo Learn . A system drawn as a real machine explains itself faster than boxes and arrows. Here is how to brief Claude Code for an interactive, accessible figure, check what it draws and ship it in your docs. An exploded isometric figure: every part sits where it really

Posted on Nov 4 • Originally published at learn.homingo.co.in • Fully Autonomous
A system drawn as a real machine explains itself faster than boxes and arrows. Here is how to brief Claude Code for an interactive, accessible figure, check what it draws and ship it in your docs.
An exploded isometric figure: every part sits where it really sits, and the reader sees the flow, not a legend.
Flowcharts are good at order: this step, then that one. They are weak at mechanism: why a request waits, where a value is kept, what fills up and what drains. An isometric drawing shows depth, so the reader sees which part sits inside which, what is in front of what, and where things connect. That is why many hardware manuals draw exploded machines rather than boxes.
Two neighbours of this guide: isometric architecture diagrams as code for whole systems, and data structure animation for stacks, queues and trees.
The figure works when the reader already knows how the object behaves. Choose the object before you think about colours or layout. A few that map well onto common software ideas:
Test the metaphor in one sentence: "Each request spends one token at the gate; with the column empty, it goes to the 429 bin." If you cannot say it in one sentence, the figure will likely need a legend, which weakens its main advantage.
Claude Code draws much better figures from a short brief than from "make a diagram of our rate limiter". The brief fixes the parts, what the reader must notice and what moves, so the first render is close and the review rounds stay short. Keep it in the repo next to the docs page, so the figure can be regenerated when the system changes.
Figure: token-bucket rate limiter, for the "Rate limits" docs page. One-sentence story: each request spends one token at the gate; with the column empty, the request drops into the 429 bin; tokens refill at a fixed rate. Parts: token column with refill pipe, gate, conveyor belt, API box at the end of the belt, 429 bin beside the gate.
The generator sorts parts back to front from their positions and splits long parts (belt, pipe) into segments. Labels: "Tokens", "Gate", "API", "429" (short, one leader line each, no overlaps). Readouts in the SVG as <tspan> elements with ids: rl-tokens-left (n/8), rl-served, rl-rejected, rl-traffic-value, rl-refill-value.
Motion: requests ride the belt; a token leaves the column each time a request passes the gate; a token drops in from the refill pipe at the refill rate; nothing else moves. Controls (page HTML, not in the SVG): sliders "Traffic" (req/s) and "Refill" (tokens/s). Look: light background, light from the top right (top faces lightest, left faces darker than right), mid-tone colours, a system font stack; text at least 12 px when rendered at the docs column width (scale font-size up if the column is narrower than 640 px).
Output: one standalone SVG, viewBox 0 0 640 400; prefix every id, class and keyframe with rl-; respect prefers-reduced-motion. Enter fullscreen mode Exit fullscreen mode
What a brief like this turns into: the rate limiter drawn as an interactive figure with our Isometric Explainers skill, readouts and sliders included.
Our Isometric explainers skill turns a brief like this into a finished figure, with layout, motion and geometry checks built in, and one command turns the same figure into a 1080x1920 reel.
Three rules decide whether the result looks crafted or broken. Put them in the prompt so Claude does not guess.
We walk through all three with a complete short script in Isometric diagrams in plain SVG: projection, four-tone shading and painter's order (on DEV).
Prompt in Claude Code (run once beforehand: npm i -D tsx)
Read docs/figures/rate-limiter.brief.md. Write docs/figures/rate-limiter.ts: a framework-independent TypeScript module that returns the figure as an SVG string. Projection: x axis down-right and y axis down-left at 30 degrees, z straight up; screenX = (x - y) * cos 30°, screenY = (x + y) * sin 30° - z (Math.cos(Math.PI / 6) and 0.5).
Shade every solid with four tones of its base colour (top lightest, right face base, left face darker, outline darkest). Order parts back to front: compare only pairs whose screen outlines overlap, use the axis separation test, sort topologically, split a part only on a real cycle. Root <svg>: xmlns, viewBox 0 0 640 400, role="img", aria-labelledby="rl-title", aria-describedby="rl-desc", with <title id="rl-title"> and <desc id="rl-desc">; prefix every id, class and keyframe with rl-; set a system font stack inside the SVG.
Add docs/figures/build.ts that writes docs/figures/rate-limiter.svg (path resolved relative to build.ts), run it with npx tsx docs/figures/build.ts, then list anything in the brief you could not do. Enter fullscreen mode Exit fullscreen mode Node 22.18 or newer can also run build.ts directly with node docs/figures/build.ts, as long as the code uses only erasable TypeScript (no enums or namespaces) and imports with explicit.ts extensions ( import { render } from './rate-limiter.ts' ); otherwise stay with tsx. If you make figures often, move the brief template and the checklist below into a skill so every figure starts the same way (see how to write a SKILL.md ).
Open the SVG at the width your docs column really shows it, not zoomed in. Then go through this list before you read any code:
Give Claude the failures as one numbered list in a single message ("1. the pipe paints over the pump; 2. the 429 label covers the gate") rather than one at a time. A clear brief keeps the review rounds short.
How you include the SVG decides what it can do. Referenced with <img src="rate-limiter.svg" alt="..."> (or Markdown image syntax), the SVG is drawn like a picture: scripts do not run and it cannot load other files (web fonts included, hence the system font stack), but CSS animation inside it still plays. That is the safe default for a static figure. For sliders and live readouts, put the SVG inline in the page: pasted into the HTML, or imported as a component in MDX with SVGR or your docs tool's SVG loader. A same-origin <object> also works: wait for its load event, then use obj.contentDocument.getElementById. The scripts below assume the inline option.
The accessible root of an inline figure, with motion that stops for readers who ask for less
<svg xmlns= "http://www.w3.org/2000/svg" viewBox= "0 0 640 400" role= "img" aria-labelledby= "rl-title" aria-describedby= "rl-desc" > <title id= "rl-title" > Token-bucket rate limiter as a machine </title> <desc id= "rl-desc" > Requests ride a belt to a gate. Each one takes a token from the column; with the column empty, the request drops into the 429 bin. Tokens refill at a fixed rate. </desc> <style>.rl-belt { stroke-dasharray: 12 12; animation: rl-roll 2s linear infinite; } @keyframes rl-roll { to { stroke-dashoffset: -24; } } @media ( prefers-reduced-motion: reduce ) {.rl-belt { animation: none; } } </style> <!-- parts, back to front --> </svg> Enter fullscreen mode Exit fullscreen mode Write the <desc> as the one-sentence story, not as a list of shapes: a screen reader user needs the mechanism, not "orange cylinder".
Styles inside an inline SVG are not scoped to it: they apply to the whole page. Prefix classes, keyframes and ids per figure ( rl- above) so two figures on one page do not clash. With SVGR, turn SVGO off for these figures ( svgo: false ) or keep its cleanupIds and removeTitle plugins disabled, so the ids, the title and the styles survive the build.
Pasted raw into an MDX page, the <style> block does not compile (a brace starts a JSX expression) and attributes need camelCase. Import the file through the loader instead, or wrap the CSS in a template string inside <style>{...}</style>. Make it interactive (only where it teaches) A slider earns its place when moving it shows the idea: drag the refill rate below the traffic rate and watch the 429s climb.
Keep the simulation in your page script and write only the numbers into the inline SVG. Text inside a role="img" figure is not read out, so give each slider an aria-valuetext, and if you announce totals, use one polite live region that updates every few seconds, never on every tick. Place the scripts after the figure, or move them to a file loaded with <script src="figure.js" defer> ( defer does nothing on an inline script).
Sliders in the page; the rl-traffic-value and rl-refill-value tspans live in the figure
<label> Traffic <input id= "rl-traffic" type= "range" min= "0.5" max= "6" step= "0.5" value= "1" ></label> <label> Refill <input id= "rl-refill" type= "range" min= "0.5" max= "4" step= "0.5" value= "1.5" ></label> <script> for ( const [ id, unit ] of [[ ' rl-traffic ', ' requests per second ' ], [ ' rl-refill ', ' tokens per second ' ]]) { const input = document. getElementById ( id ); const show = () => { input. setAttribute ( ' aria-valuetext ', input. value + ' ' + unit ); document. getElementById ( id + ' -value ' ). textContent = input. value; }; input. addEventListener ( ' input ', show ); show (); } </script> Enter fullscreen mode Exit fullscreen mode A minimal token bucket behind the figure: capacity 8, ticks ten times a second
const still = matchMedia ( ' (prefers-reduced-motion: reduce) ' ). matches; const el = ( id ) => document. getElementById ( id ); const traffic = el ( ' rl-traffic ' ), refill = el ( ' rl-refill ' ); const sim = { tokens: 8, due: 0, served: 0, rejected: 0 }; let last = performance. now (); setInterval (() => { const now = performance. now (), dt = ( now - last ) / 1000; last = now; sim. tokens = Math. min ( 8, sim. tokens + Number ( refill. value ) * dt ); sim. due += Number ( traffic. value ) * dt; for (; sim. due >= 1; sim. due -= 1 ) { if ( sim. tokens >= 1 ) { sim. tokens -= 1; sim. served += 1; } else sim. rejected += 1; } el ( ' rl-tokens-left ' ). textContent = Math. floor ( sim. tokens ) + ' /8 '; el ( ' rl-served ' ). textContent = sim. served; el ( ' rl-rejected ' ). textContent = sim. rejected; if (! still ) { /* move the requests and tokens in the figure here */ } }, 100 ); Enter fullscreen mode Exit fullscreen mode With reduced motion on, the numbers still update; only the parts stay put. Set traffic to 4 and refill to 1: the column empties within a few seconds and from then on about three requests in four land in the 429 bin.
Slides, social cards and some docs tools want a bitmap. Headless Chrome renders the standalone SVG (your page CSS does not apply, and the font named in the SVG must be installed on the machine that renders it). --force-device-scale-factor=2 gives a sharp 2x image and --force-prefers-reduced-motion keeps animated parts still. Run it from docs/figures:
Linux: google-chrome, chromium or chromium-browser. macOS: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome". Windows: "C:\Program Files\Google\Chrome\Application\chrome.exe" with a file:///C:/... URL. Spaces in the path must be written as %20 in the URL.
google-chrome --headless --screenshot = rate-limiter.png \ --window-size = 640,400 --force-device-scale-factor = 2 \ --force-prefers-reduced-motion \ "file:// $PWD /rate-limiter.svg" Enter fullscreen mode Exit fullscreen mode Match --window-size to the SVG's viewBox so nothing is cropped or padded. For link previews, render a separate 1200x630 framing of the same figure rather than stretching the docs version.
To take a figure to video, see exploded view animation for explainer reels, or in Hinglish, educational animation video kaise banaye.
Isometric explainers skill: Our Claude Code agent skill for crafted, interactive isometric figures: live readouts, dark and light themes, timed callouts and built-in layout, motion and geometry checks. One command turns the same figure into a 1080x1920 explainer reel with camera moves, cutaways and callouts. Explain a mechanism as a real, well-made machine. See the skill
Disclosure: this guide is by Homingo, the team behind the skill linked above. Everything in it works without it.