An Agent Skill that renders diagrams, slides, social cards and animated sequence GIFs to PNG by authoring HTML and screenshotting it with headless Chrome. No design tool, no API, no npm dependencies.
![]()
Ask your coding agent for an architecture diagram. Go on, try it right now.
You will get ASCII art. Or a Mermaid block that renders as three grey boxes with the labels overlapping. Or, if the model is feeling honest, a paragraph describing what the diagram would look like if it could draw one.
Your agent can write a parser, refactor a service and explain a race condition it has never seen before. It cannot draw a rectangle.
That gap bothered me enough to close it.
That diagram was not drawn in Figma. My agent wrote an HTML file and a script screenshotted it. So did every other image in this post, including the cover.
What it actually does
render-visual is an Agent Skill: a folder of instructions and scripts that any skills-compatible agent can load. Claude Code, Cursor, GitHub Copilot, Codex, Gemini CLI, OpenCode, Amp and Goose all read the same format.
Once it is installed you stop asking for code and start asking for pictures:
"make a diagram of our auth flow"
"turn these notes into a 6-slide deck, paper theme"
"an og card for this repo"
"put this screenshot in a browser frame"
Thirteen templates ship with it: architecture diagrams, swimlanes, trees, cluster diagrams, deployment diagrams, mind maps, sequence diagrams (static or animated), code windows, slides at 1920x1080, and social cards at 1200x630.
Try it in about thirty seconds
You need Node 18 or newer and any Chromium based browser. Chrome, Chromium, Brave and Edge all work, and you almost certainly have one already.
For Claude Code:
claude plugin marketplace add imshaikot/render-visual-skill
claude plugin install render-visual-skill@render-visual-skill
For everything else, clone the published skill branch straight into your skills folder:
git clone --depth 1 -b skill https://github.com/imshaikot/render-visual-skill.git \
~/.agents/skills/render-visual
That is the whole install. There is no npm install step, because there are no dependencies.
How it works
The pipeline is deliberately boring, which is the point.

Your agent copies a template, replaces the placeholder content, and runs one command:
node ~/.agents/skills/render-visual/scripts/render.mjs figure.html figure.png --theme slate
The canvas size comes from the CSS on the page body. Scale defaults to 2, so a 1360x740 canvas becomes a 2720x1480 PNG that holds up on a retina display.
Authoring figures as HTML buys three things that a design tool cannot:
- They are diffable. A figure is a text file in your repo. Regenerating it after a copy change is one command, not forty minutes of nudging boxes.
- They are consistent by construction. Templates consume design tokens and never hard code a colour, so nothing gets hand picked per image.
- Agents are good at it. A model writes HTML far better than it steers a canvas.
The part I actually care about
Here is the thing that shaped every design decision in this project.
An agent cannot see its own output. If a render silently produces a blank white PNG, the agent reports success, moves on, and you find out three steps later when the image lands in your README. A wrong image at exit 0 is the worst outcome this pipeline can produce. It is worse than a crash, because a crash is information.
So the renderer is aggressively paranoid, in two phases you can see in the diagram above.
Before Chrome launches, everything the page needs is resolved rather than left for the browser to fetch. A missing stylesheet is fatal. An unknown element id is fatal, and the error lists the ones that exist. An image is read off disk, format checked and inlined as a data URI. A file that is really a text file with a .png extension is fatal, naming the path.
Why bother? Because every one of those failures otherwise produces a page that renders perfectly and screenshots as a flawless success, with an invisible hole where your content should be.
After the screenshot, the PNG is decoded and inspected. If the luminance spread says nothing but background got painted, it is rejected. Truncated files are rejected. Wrong dimensions are rejected. The bad file is left on disk so you can look at it.
Every guard exists because something once succeeded quietly and wrongly. Twenty two invariants in the self test suite keep them honest.
Themes are just tokens
Eight themes ship with it. Templates consume tokens only, so one source file renders in any of them. Same markup, one flag:
Every colour token also ships a component twin, three bare OKLCH numbers, so any transparency of any accent is one expression away:
.badge {
background: oklch(var(--a1-raw) / 12%);
border: 1px solid oklch(var(--a1-raw) / 45%);
color: var(--a1);
}
The solid token is built from those same components, so a wash can never drift from the colour it is a wash of. An invariant refuses any theme whose component tokens are not composable, because a broken one paints nothing rather than failing.
Adding a theme is one CSS file defining the same token set.
57 elements you reference, not copy
Figures assemble from a library of parts: browser and phone and terminal frames, databases, servers, queues, routers, a 3D deployment cube, thirty icon glyphs, and the full chart vocabulary. A figure references one instead of carrying a copy of its geometry:
<g data-part="el-database" data-accent="2" transform="translate(70,452)"/>
There are chart and BI parts too, but with an important caveat worth repeating loudly: these are schematics of charts, not charts. Every proportion in them is fixed and arbitrary. They exist so a figure can say "a dashboard goes here" the way a cylinder says "a database goes here". If your numbers are the point, plot them with a real charting library.
Animated GIFs, with no ffmpeg
This is my favourite corner of the codebase. Sequence diagrams can animate, one step at a time:
There is no ffmpeg, no encoder binary, no dependency. Chrome renders the frames in parallel, Node's built in zlib decodes the PNGs, a median cut quantizer builds a shared palette with ordered dithering, and a hand rolled GIF89a and LZW encoder assembles the result. Frames after the first store only the changed region as transparent pixel deltas, so tween frames cost almost nothing.
GIF is a 1989 format. It turns out you really do not need a toolchain for it.
What it deliberately does not do
Being clear about the edges is more useful than overselling:
- It is not a charting library. Give it a dataset and it will still draw a schematic. Use a plotting library.
- It needs a shell and a local browser. That rules out claude.ai chat, the Skills API and most CI images.
- Theme fonts come from Google Fonts. Offline renders still succeed, but they fall back to system fonts and will not match the previews. Inlining the fonts as data URIs is on the list.
Give it a go
claude plugin marketplace add imshaikot/render-visual-skill
claude plugin install render-visual-skill@render-visual-skill
Then just ask for a diagram.
The repo is imshaikot/render-visual-skill, MIT licensed. Issues and pull requests welcome, and I am especially interested in new themes, since each one is a single CSS file.
If you build something with it, I would genuinely like to see it.




Top comments (0)