icon-draw
Construct candidate icons as clean SVG code: symmetric glyph on a dark grey
rounded rectangle, geometry so disciplined that every candidate passes a
deterministic linter before a human ever sees it. LLM one-shot SVG fails in
known ways (bloated paths, fake <image> vectors, over-precise coords) — this
skill exists to prevent exactly those failures.
When NOT to use
- Concept exploration / "what should the icon be" →
icon-brief. - Reviewing renders or fixing an existing icon's small-size problems →
icon-critique. - Producing favicon/store/asset files →
icon-export. - UI icon sets, illustrations, wordmarks, or converting/vectorizing raster logos.
Workflow
Get the brief. Read
icon-design/brief.mdif it exists. If not, write a 5-line quick brief (product, concept + one Gestalt device, symmetry axis, palette) toicon-design/brief.mdmarked "quick brief" — so critique/export have provenance — and state it to the user; for anything ambitious, recommendicon-briefinstead. Never draw without a stated concept.Start from the template. Copy
assets/master-template.svg(relative to this skill): squareviewBox="0 0 1024 1024"and a background<path id="bg">that is a real squircle — Apple's corner is a continuous curve, not a circular arc, so a plainrxrect renders subtly wrong beside system icons. Never hand-write or hand-edit that path; regenerate it:python3 "${CLAUDE_SKILL_DIR}/scripts/squircle.py" # iOS 26+ (radius 25.78%, smoothing 0.6) python3 "${CLAUDE_SKILL_DIR}/scripts/squircle.py" --legacy # iOS 7-18 (22.37%)Both radii are community measurements, not Apple-published constants —
references/icon-geometry.mdhas the provenance. Read it before changing the shape.Draw the object the brief names, on the 64-unit grid. Coordinates land on multiples of 64/32/16 wherever possible; max 2 decimals ever. Use a primitive when the shape is a primitive (a disc is a
<circle>, a bar is a<rect>) and a<path>with absolute commands when the subject has a silhouette — one well-made path that reads as a drop, an arch or a bird beats five primitives arranged into something that only reads as an arrangement. Budget: 1 stroke width (2 max, ≥ 48 units), ≤ 3 colors total. There is no shape-count budget — element count was measured against a 66-mark ballot and predicted nothing. What predicts survival is mass:- Fill, don't outline. A filled silhouette with knockout details beats an outline of the same object. Pure-fill marks were rejected at half the rate of marks carrying a stroked element.
- Aim for ~20% ink coverage at 64 px.
render_icon.pyprintsink=for every render; the heaviest third of that ballot had a zero rejection rate, the thinnest third was rejected at twice the base rate. - Cut detail out of the mass rather than adding parts beside it — background-coloured knockouts inside a solid body (figure-ground) is the construction that won most often.
- Symmetry by construction (vertical axis): draw axis-straddling parts
centered on x=512, put off-axis parts in
<defs><g id="half">…</g></defs>and render with<use href="#half"/><use href="#half" transform="translate(1024,0) scale(-1,1)"/>. For a horizontal axis usetranslate(0,1024) scale(1,-1). - Glyph size: max dimension ~460–655 units (45–64% of canvas), but the centered r=409 safe circle overrides the band: a square-ish glyph bbox must stay ≤ ~578 units unless its filled geometry avoids the bbox corners — this is what keeps the full-square export alive under PWA/Android masking.
- Stay inside the safe static subset (
references/svg-subset.md): no text, gradients, filters, CSS,@media, scripts, external refs, or raster images.
Draw 2–4 candidates — one per brief concept (or deliberate variations of one concept), each a separate file:
icon-design/candidates/<n>-<slug>.svg. Never overwrite an existing candidate — continue from the next free number. Vary silhouette between candidates, not just sizes of the same idea.Lint every candidate:
python3 "${CLAUDE_SKILL_DIR}/scripts/check_svg.py" icon-design/candidates/1-<slug>.svg --axis v(
--axis hfor horizontal symmetry,--axis nonewhen the brief declares an organic subject.) Fix every ERROR — those are the safe-subset and output-contract checks, and a candidate that trips one is unusable. Weigh the WARNs — symmetry, centring, glyph size, stroke weight and maskable safe zone are house guidance, not truth. Report each one you leave standing and why. Deforming a mark that reads well in order to silence a geometry warning is the wrong trade;--strictpromotes them all to errors if you want the old fail-closed behaviour for a batch.Hand off. Present the candidates (paths + one-line rationale each) and recommend
icon-critiqueto render and review them. Do not declare the icon final and do not export — approval happens after critique.
Output spec
icon-design/candidates/<n>-<slug>.svg— 2–4 files, each passingcheck_svg.pywith 0 errors; warnings resolved or explained.- Each candidate ≤ ~40 lines of SVG; human-readable ids (
bg,glyph,half). - A short summary: per candidate, concept + Gestalt device + expected 16px risk.
Gotchas
- The rounded-rect background is for self-rendered surfaces (favicons,
README, GitHub). iOS/App Store submissions need the full-square variant —
icon-exportderives it by setting the backgroundrxto 0. Never bake a second rounding into exports. - Mirrored
<use>halves: keep fills fully opaque — semi-transparent overlaps double up where halves meet; and don't let the half cross the axis (the mirror doubles anything that straddles it). fill="none"without astrokerenders nothing — a classic blank-icon cause.- Pure geometric centering can look off; optical centering (nudge visually heavy side ≤ 8 units) is allowed but keep the bbox center within the linter's ±6 tolerance — prefer shape rebalancing over nudges.
- Don't imitate existing marks (Docker whale, GitHub cat…): trademark risk — flag it and propose a distinct silhouette for the same metaphor.
- Letterform glyphs:
<text>is banned (font availability breaks renders); drawing a letter as paths is allowed but costs legibility at 16px — say so. - The linter can't judge beauty: passing it means valid, not good. Worse, the correlation can run backwards — in this repo's own bake-off the lint-cleanest candidates were ranked last by a human and the two that failed the linter placed first and second. Use it to catch what would break, never as evidence that a mark is working. Critique is a separate, mandatory step.
- Symmetry is a default, not a virtue. It buys stability and cheap
construction on geometric subjects. On an organic subject it deforms the
silhouette into something abstract — take
--axis nonefrom the brief and draw the animal. - If the linter can't run (no
python3, missing script), fail closed: label the candidates UNVALIDATED, report the gap, and don't hand them to critique/export as if they had passed.