unDraw illustrations
Find an illustration on undraw.co and save it into the project, recolored to the project's theme.
For a small hand-drawn accent instead — an arrow, underline, circle or doodle that points at something rather than filling a space — use the
undraw-handcraftsskill.
Locating the script
The bundled CLI is at scripts/undraw.mjs in the plugin root — two levels up
from this file. In Claude Code use ${CLAUDE_PLUGIN_ROOT}/scripts/undraw.mjs.
On other hosts, resolve it relative to this SKILL.md's directory:
<skill-dir>/../../scripts/undraw.mjs.
Below it is written as $UNDRAW. That is a placeholder in this document, not a
variable that exists in your shell — substitute the real path before running
anything. In PowerShell especially, an unset $UNDRAW expands to an empty
string and node "" fails with a confusing error.
Shell snippets below are POSIX. On Windows the host may be PowerShell, where
&&,grep, and2>/dev/nulldo not work — prefer your own file-search and file-read tools for the inspection steps, and use the shell only to runnode.
Step 1 — Check the runtime
Run these as two separate commands (&& is a parse error in PowerShell 5.1):
node --version
Then confirm $UNDRAW exists using whatever file tool you have.
Node 18+ is required (the script uses the global fetch).
If either check fails — Node missing or older than 18, or the script not
found because only the skill directory was copied into this host — read
references/manual-fallback.md and follow it instead. It covers the whole flow
with curl alone. Otherwise do not read that file; the steps below are
sufficient.
Step 2 — Search
Turn the user's request into 1–3 concrete keywords. undraw.co matches on illustration titles, so search for the subject ("login", "security", "empty cart"), not the page purpose ("signup form for our SaaS").
node "$UNDRAW" search "login" --limit 8
If nothing matches, try a broader synonym before reporting failure. Exit code 3 means the query returned nothing.
--limit is capped at 50, and a search stops after 20 pages or as soon as a
page returns nothing new — so a result count below --limit means the catalog
ran out, not that something failed. Quote the query: an unquoted multi-word
argument is rejected rather than silently searched by its first word.
Step 3 — Let the user pick
Present the results as a numbered list with each title and its preview URL. The terminal cannot render the images, so the URL is how the user actually sees the illustration — always include it.
1. Biometric Login — https://undraw.co/illustration/biometric-login_v832
2. Secure login — https://undraw.co/illustration/secure-login_m11a
Ask which one they want. Do not pick for them unless they explicitly said to.
Step 4 — Propose a save path
Find which of these the project already has: public/, src/assets/,
static/, app/assets/, assets/. Use your file-listing tool rather than a
shell command so this works on any host.
Propose the conventional one for the framework in use and confirm before writing. Never invent a new directory without asking.
Step 5 — Propose a color
Look in the stylesheet first — since Tailwind v4, theme colors live in CSS, and
tailwind.config.* often does not exist at all.
Search the project's stylesheets for --primary, --color-primary, or
--brand with your content-search tool. The equivalent POSIX command, if you
prefer the shell:
grep -rnE "^\s*--(primary|color-primary|brand)\s*:" \
src/app/globals.css app/globals.css src/styles.css src/index.css 2>/dev/null
Where to look, in order:
- CSS custom properties in
globals.css/styles.css/index.css/theme.css—--primary,--color-primary,--brand. Inside a@theme { }block (Tailwind v4) or:root { }. tailwind.config.*→theme.colors.primary— only Tailwind v3 and earlier.
The value is often not a hex string. Common forms:
| Declared as | Convention |
|---|---|
--primary: 214 92% 58% |
shadcn/ui — bare HSL channels, wrapped elsewhere by hsl(var(--primary)) |
--primary: oklch(0.514 0.222 16.935) |
Tailwind v4 default palette |
--color-primary: hsl(var(--primary)) |
an indirection — follow it to the real value |
Pass whatever you found straight to --color; the script converts HSL and RGB
to hex and passes CSS Color 4 functions through. Do not convert by hand.
Show the value you found and confirm before writing. If nothing is found, offer
unDraw's default #6c63ff or one of the site's presets:
#17B8A6 #38bdf8 #F50057 #f59e0b #dadada.
Step 6 — Save
Pass the media URL from the search output verbatim:
Keep it on one line — a trailing \ is not a line continuation in PowerShell:
node "$UNDRAW" get "https://cdn.undraw.co/illustration/biometric-login_v832.svg" --out public/illustrations --color "#3b82f6"
--out is treated as a file only when it ends in .svg. Anything else is a
directory and is created if missing, with the filename derived from the slug. So
--out public/illustrations/login-hero makes a directory named login-hero —
write login-hero.svg if you meant a file.
If the user asks to replace an existing non-SVG asset (hero.png), the command
refuses with exit 1 rather than overwriting it. Save the SVG alongside and
update the reference in the code instead.
Report the saved path. Two outputs need passing on rather than swallowing:
Warning: this illustration contains no #6c63ffon stderr means the recolor matched nothing and the file kept its original colors. Say so; do not report a recolor that did not happen.- Exit code
4means the download succeeded but the file could not be written (permissions, a path collision). Nothing was saved.
If the user is working on a component, offer to wire the file in — but only after it exists on disk.
Rules
- Never build a CDN URL from a slug. Path segments are inconsistent across
the catalog (
/illustration/vs/illustrations/); only themediafield fromsearchis correct. Assembling URLs 404s on roughly half the library. - Confirm the path and color before writing. Both are guesses until the user agrees.
- unDraw's license is permissive about using assets but restricts automated acquisition — see the Licensing section of the plugin README before adding any bulk-download behavior.