
Icon Forge
- 1 installs
- 21 repo stars
- Updated August 5, 2026
- joaquimscosta/arkhe-claude-plugins
Generates icons from a prompt.
About
Generates icon assets. A developer uses it when creating icons for a UI or brand.
- Icon generation from a prompt
Icon Forge by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,609 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joaquimscosta/arkhe-claude-plugins --skill icon-forgeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 21 |
| Last updated | August 5, 2026 |
| Repository | joaquimscosta/arkhe-claude-plugins ↗ |
What it does
Generates icons from a prompt.
Files
Icon Forge
Generate brand icons as SVG and produce all required platform assets from a single source.
Quick Start
Follow these phases in order. Skip to Phase 4 if user provides --svg <path>. Use --base <path> to load an existing SVG as a design seed for Phase 2 iteration.
Phase 1: Brand Discovery
Gather brand information before designing. Ask about:
- Identity: Brand name, industry, tagline
- Concept: Visual metaphor, abstract vs literal, symbol ideas
- Colors: Primary color (hex), secondary, accent
- Style preset: Present the style menu:
1. Geometric — clean shapes, mathematical precision 2. Organic — flowing curves, irregular blobs, natural asymmetry 3. Illustrative — layered scenes, color blocks, story-driven 4. Symbolic — dual-meaning line art, negative space, conceptual merges 5. Constellation — connected nodes, network graphs, dot clusters
- Depth: Flat (default) or with gradients/shadows?
If $ARGUMENTS contains a brand description, extract info and minimize questions.
Phase 2: Design Master SVG
Generate 2-3 concept variations as SVG. Apply the chosen style preset's SVG techniques from WORKFLOW.md (see Style-to-SVG Technique Table). Design for three progressive detail tiers: Glyph (16px, 2-4 shapes), Mark (192px, full logomark), Master (1024px, rich detail). Present concepts, let user choose, iterate.
If `--base <path>` was provided: Read the existing SVG, analyze its shapes/colors/structure, and use it as a starting point instead of generating from scratch. Present the original alongside 2 improved variations that apply the chosen style preset. See WORKFLOW.md "Design Seed Workflow" for details.
SVG structure requirements:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="icon-title">
<title id="icon-title">Brand Name</title>
<style>
.primary { fill: #2563eb; }
.accent { fill: #1e40af; }
</style>
<path class="primary" d="..."/>
</svg>Validation checklist:
- viewBox is square (
0 0 100 100brand icons;0 0 24 24for UI-style marks) - No
width/heightattributes on root<svg> <title>as first child,role="img"on root (accessibility)- No
<text>elements (text does not scale to 16px) - Filled shapes on integer coordinates (prevents sub-pixel blur)
- No strokes thinner than 2 units in Glyph/Mark tiers
- Color count within preset limit (1-3 most; up to 5 Illustrative/Constellation)
xmlnsattribute present
Phase 3: Create Dark-Mode Favicon SVG
Duplicate the master SVG and embed a @media (prefers-color-scheme: dark) block:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="fav-title">
<title id="fav-title">Brand Name</title>
<style>
.bg { fill: #ffffff; }
.fg { fill: #1a1a2e; }
@media (prefers-color-scheme: dark) {
.bg { fill: #1a1a2e; }
.fg { fill: #e0e0ff; }
}
</style>
<rect class="bg" width="100" height="100" rx="12"/>
<path class="fg" d="..."/>
</svg>Rules: dark foreground becomes light, light backgrounds become dark, maintain >= 4.5:1 contrast.
Phase 3b: Monochrome Variant
Duplicate the master SVG and replace all colors with currentColor. Remove <style>, gradients, and filters — produce a single-color silhouette that inherits its color from CSS. Save as monochrome.svg. See WORKFLOW.md for details.
Phase 4: Generate Platform Assets
Save master SVG and dark-mode SVG to the project.
Framework detection — before running the script, detect the target project: 1. If next.config.(js|mjs|ts) exists AND app/layout.(tsx|jsx|js) exists → add --framework nextjs 2. Otherwise → omit --framework (default generic output)
uv run <skill-scripts-dir>/generate_assets.py \
--svg <master-svg-path> \
--dark-svg <dark-svg-path> \
--bg-color "<brand-bg-color>" \
--name "<app-name>" \
--framework nextjs \ # omit if not Next.js App Router
--output-dir ./brand-assetsReplace <skill-scripts-dir> with the absolute path to this skill's scripts/ directory. Omit --framework for non-Next.js projects. With --framework nextjs, outputs icon.svg and apple-icon.png (Next.js App Router conventions).
Requires rsvg-convert (brew install librsvg) or magick (brew install imagemagick).
Phase 5: Integration Output
Present to the user: 1. The integration guide (_nextjs-guide.txt for Next.js, or _html-snippet.html for other frameworks) 2. Framework-specific placement guidance (Next.js app/ + public/, Vite public/, CRA public/) 3. Summary of all generated files
SVG Icon Design Principles
1. Canvas: Square viewBox — 0 0 100 100 for brand icons (default), 0 0 24 24 for UI-style marks. No width/height attributes 2. Scalability: Must be recognizable at 16px (favicon) through 1024px (app store) 3. Shape vocabulary: Match shapes to the chosen style preset. See WORKFLOW.md for style-specific SVG techniques 4. Fill vs stroke: Prefer filled paths (scale predictably); use strokes for Symbolic line art. See WORKFLOW.md for per-preset strategy 5. Stroke minimum: No strokes thinner than 2 units in Glyph/Mark tiers; Master tier may use 1.5+ for decorative detail 6. Color restraint: 1-3 brand colors for most presets; Illustrative and Constellation may use up to 5 7. No text: Logomark only — text does not survive 16px rendering 8. Progressive detail: Design for three tiers (Glyph 16px, Mark 192px, Master 1024px). Fine detail welcome in Master tier; must simplify gracefully 9. Pixel alignment: Integer coordinates for filled shapes; 0.5 offset for odd stroke widths. Limit decimals to 1-2 places 10. Accessibility: <title> as first child with brand name, role="img" on root <svg> 11. Visual weight: Center of mass should feel balanced in the square canvas 12. Negative space: Use intentionally for clever dual-meaning designs 13. currentColor: Always generate a monochrome variant with fill="currentColor" alongside the branded master 14. Depth: Flat by default. When depth is enabled, use <linearGradient>, <radialGradient>, and subtle <filter> effects 15. Rounded corners: Use rx/ry for approachable feel when appropriate
Output
See WORKFLOW.md for detailed workflow and EXAMPLES.md for examples. See TROUBLESHOOTING.md for common issues.
Examples
Real-world examples demonstrating different style presets.
---
Example 1: "Deznode" — Constellation Preset with Depth
User Input
/icon-forge Dev tools platform called "Deznode" - network/constellation feel, warm accents on dark, depth enabledBrand Discovery (extracted from args)
- Name: Deznode
- Industry: Developer tools
- Colors: Navy dark (#0f1729), Yellow (#f5a623), Coral red (#e74c5e), Slate gray (#6b7d99)
- Style preset: Constellation
- Depth: Enabled
Concept Options
1. Neural Network — Central chevron hub with organic blob-nodes radiating outward via curved connections. Nodes pulse outward at varied sizes. Progressive complexity: glyph shows just the chevron + 2 accent dots. 2. Protocol Graph — Interconnected nodes forming a loose graph structure. Each node is an irregular blob, not a perfect circle. Connections curve naturally between them. 3. Code Constellation — Stars/dots of varied sizes arranged in a constellation pattern, with a bold chevron cutting through the center.
Selected: Concept 1 (Neural Network)
Generated Master SVG
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="deznode-title">
<title id="deznode-title">Deznode</title>
<defs>
<radialGradient id="glow-red" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ff6b7a"/>
<stop offset="100%" stop-color="#e74c5e"/>
</radialGradient>
<radialGradient id="glow-yellow" cx="50%" cy="40%" r="60%">
<stop offset="0%" stop-color="#ffd06b"/>
<stop offset="100%" stop-color="#f5a623"/>
</radialGradient>
</defs>
<style>
.node-slate { fill: #6b7d99; }
.node-dark { fill: #3a4a63; }
.conn { fill: none; stroke: #3a4a63; stroke-width: 1.8; opacity: 0.6; }
.conn-warm { fill: none; stroke: #e74c5e; stroke-width: 1.5; opacity: 0.4; }
.chevron { fill: url(#glow-yellow); }
.dot-red { fill: url(#glow-red); }
</style>
<!-- Connections — curved bezier paths, NOT straight lines -->
<path class="conn" d="M 38,42 C 30,30 22,28 18,32"/>
<path class="conn" d="M 38,48 C 28,55 20,62 18,68"/>
<path class="conn" d="M 62,42 C 68,30 78,22 82,25"/>
<path class="conn-warm" d="M 62,52 C 70,58 76,62 80,65"/>
<path class="conn" d="M 50,38 C 48,28 42,18 38,14"/>
<!-- Blob nodes — irregular shapes, NOT perfect circles -->
<path class="node-slate" d="M 14,28 C 12,24 16,20 20,22 C 24,24 24,30 22,34 C 20,38 14,36 12,32 Z"/>
<path class="node-dark" d="M 12,64 C 10,60 14,56 18,58 C 22,60 24,66 22,70 C 20,74 14,72 12,68 Z"/>
<path class="node-slate" d="M 78,20 C 76,16 80,12 84,14 C 88,16 90,22 88,26 C 86,30 80,28 78,24 Z"/>
<path class="node-dark" d="M 34,8 C 32,4 36,2 40,4 C 44,6 44,12 42,16 C 40,18 34,16 34,12 Z"/>
<!-- Accent nodes with radial gradient glow -->
<circle class="dot-red" cx="82" cy="65" r="7"/>
<circle class="dot-red" cx="22" cy="78" r="5" opacity="0.7"/>
<!-- Central chevron — the glyph-tier anchor -->
<path class="chevron" d="M 38,35 L 55,50 L 38,65 L 44,65 L 62,50 L 44,35 Z"/>
<!-- Particle trail behind chevron -->
<circle fill="#f5a623" cx="30" cy="46" r="1.5" opacity="0.8"/>
<circle fill="#f5a623" cx="26" cy="48" r="1.2" opacity="0.6"/>
<circle fill="#f5a623" cx="22" cy="50" r="1" opacity="0.4"/>
<circle fill="#f5a623" cx="32" cy="52" r="1.3" opacity="0.7"/>
<circle fill="#f5a623" cx="28" cy="54" r="1" opacity="0.5"/>
</svg>Glyph-Tier Favicon Variant
At 16-32px, the nodes and connections disappear. Only the chevron and accent dots survive:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="deznode-fav">
<title id="deznode-fav">Deznode</title>
<style>
:root { color-scheme: light dark; }
.chevron { fill: #f5a623; }
.dot { fill: #e74c5e; }
@media (prefers-color-scheme: dark) {
.chevron { fill: #ffd06b; }
.dot { fill: #ff6b7a; }
}
</style>
<path class="chevron" d="M 28,28 L 58,50 L 28,72 L 38,72 L 68,50 L 38,28 Z"/>
<circle class="dot" cx="76" cy="38" r="7"/>
<circle class="dot" cx="76" cy="62" r="5"/>
</svg>Monochrome Variant (Phase 3b)
All colors replaced with currentColor — inherits color from CSS context:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="deznode-mono">
<title id="deznode-mono">Deznode</title>
<!-- Connections -->
<path fill="none" stroke="currentColor" stroke-width="1.8" opacity="0.4" d="M 38,42 C 30,30 22,28 18,32"/>
<path fill="none" stroke="currentColor" stroke-width="1.8" opacity="0.4" d="M 62,42 C 68,30 78,22 82,25"/>
<!-- Blob nodes as filled currentColor -->
<path fill="currentColor" opacity="0.3" d="M 14,28 C 12,24 16,20 20,22 C 24,24 24,30 22,34 C 20,38 14,36 12,32 Z"/>
<path fill="currentColor" opacity="0.3" d="M 78,20 C 76,16 80,12 84,14 C 88,16 90,22 88,26 C 86,30 80,28 78,24 Z"/>
<!-- Central chevron -->
<path fill="currentColor" d="M 38,35 L 55,50 L 38,65 L 44,65 L 62,50 L 44,35 Z"/>
<!-- Accent dots -->
<circle fill="currentColor" cx="82" cy="65" r="7" opacity="0.6"/>
</svg>Script Invocation
uv run .../generate_assets.py \
--svg ./master-icon.svg \
--dark-svg ./favicon.svg \
--bg-color "#0f1729" \
--name "Deznode" \
--theme-color "#f5a623" \
--output-dir ./brand-assets---
Example 2: "Skola.dev" — Illustrative Preset with Depth
User Input
/icon-forge Educational platform "Skola.dev" - learning, warm colors, illustrative, depthBrand Discovery (extracted from args)
- Name: Skola.dev
- Industry: Education / Developer learning
- Colors: Coral (#ff6b6b), Teal (#4ecdc4), Gold (#f7b731), Deep navy (#1a1a2e)
- Style preset: Illustrative
- Depth: Enabled
Concept Options
1. Code Sailboat — A warm-toned sailboat with layered color-block sails. The hull is a gentle curve. Represents a journey of learning — setting sail into code. Layered <g> groups build the scene. 2. Wave of Knowledge — A flowing organic wave with embedded code brackets < >. Warm gradient from coral to teal. 3. Compass Rose — A stylized compass with code symbols at cardinal points. Warm color palette.
Selected: Concept 1 (Code Sailboat)
Generated Master SVG
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="skola-title">
<title id="skola-title">Skola.dev</title>
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffe8cc"/>
<stop offset="100%" stop-color="#ffd4a8"/>
</linearGradient>
<linearGradient id="water" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#4ecdc4"/>
<stop offset="100%" stop-color="#3ab5ad"/>
</linearGradient>
</defs>
<style>
.sail-coral { fill: #ff6b6b; }
.sail-gold { fill: #f7b731; }
.hull { fill: #1a1a2e; }
.mast { fill: #1a1a2e; }
.flag { fill: #4ecdc4; }
</style>
<!-- Background: warm sky gradient -->
<rect fill="url(#sky)" width="100" height="100" rx="8"/>
<!-- Water layer — organic wave, not a straight line -->
<g class="water-layer">
<path fill="url(#water)" d="M 0,62 C 15,58 30,64 50,60 C 70,56 85,63 100,59 L 100,100 L 0,100 Z"/>
<!-- Secondary wave for depth -->
<path fill="#3ab5ad" opacity="0.5" d="M 0,68 C 20,64 40,70 60,66 C 80,62 90,67 100,65 L 100,100 L 0,100 Z"/>
</g>
<!-- Sailboat -->
<g class="boat" transform="translate(2, 0)">
<!-- Mast -->
<rect class="mast" x="47" y="18" width="3" height="45" rx="1"/>
<!-- Main sail — coral color block -->
<path class="sail-coral" d="M 50,20 C 65,28 72,42 68,58 L 50,58 Z"/>
<!-- Jib sail — gold color block, overlapping -->
<path class="sail-gold" d="M 47,22 C 32,30 28,45 30,56 L 47,56 Z"/>
<!-- Hull — smooth organic curve -->
<path class="hull" d="M 28,60 C 30,56 38,54 50,54 C 62,54 70,56 72,60 C 68,66 32,66 28,60 Z"/>
<!-- Small flag at mast top -->
<path class="flag" d="M 50,18 L 58,14 L 50,11 Z"/>
</g>
<!-- Subtle wave foam dots -->
<circle fill="#ffffff" cx="20" cy="62" r="1" opacity="0.6"/>
<circle fill="#ffffff" cx="75" cy="60" r="1.2" opacity="0.5"/>
<circle fill="#ffffff" cx="90" cy="63" r="0.8" opacity="0.4"/>
</svg>Dark-Mode Favicon SVG
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="skola-fav">
<title id="skola-fav">Skola.dev</title>
<style>
:root { color-scheme: light dark; }
.bg { fill: #faf5ef; }
.sail1 { fill: #ff6b6b; }
.sail2 { fill: #f7b731; }
.hull { fill: #1a1a2e; }
.wave { fill: #4ecdc4; }
@media (prefers-color-scheme: dark) {
.bg { fill: #1a1a2e; }
.sail1 { fill: #ff8a8a; }
.sail2 { fill: #ffd06b; }
.hull { fill: #e0e0ff; }
.wave { fill: #5ee6dc; }
}
</style>
<rect class="bg" width="100" height="100" rx="12"/>
<!-- Simplified sailboat for favicon -->
<rect class="hull" x="47" y="20" width="3" height="42" rx="1"/>
<path class="sail1" d="M 50,22 C 64,30 68,45 66,58 L 50,58 Z"/>
<path class="sail2" d="M 47,24 C 34,32 30,46 32,56 L 47,56 Z"/>
<path class="hull" d="M 30,60 C 32,56 40,54 50,54 C 60,54 68,56 70,60 C 66,66 34,66 30,60 Z"/>
<path class="wave" d="M 0,68 C 20,63 50,70 80,64 C 90,62 100,66 100,66 L 100,100 L 0,100 Z"/>
</svg>Script Invocation (Next.js detected)
uv run .../generate_assets.py \
--svg ./master-icon.svg \
--dark-svg ./favicon.svg \
--bg-color "#faf5ef" \
--name "Skola.dev" \
--theme-color "#ff6b6b" \
--framework nextjs \
--output-dir ./brand-assets---
Example 3: "Papia Studio" — Symbolic Preset, Flat
User Input
/icon-forge "Papia Studio" - language preservation tools for Cape Verdean Kriolu, symbolic, flatBrand Discovery (extracted from args)
- Name: Papia Studio
- Industry: Language tools / Cultural preservation
- Colors: Indigo (#4355db), Soft white (#f5f5f7)
- Style preset: Symbolic
- Depth: Flat
Concept Options
1. Speech Pen — A pen nib merged with a speech bubble. The pen represents writing/creation; the bubble represents spoken language. A small star inside symbolizes the spark of preservation. Uses fill-rule="evenodd" for the star cutout. 2. Sound Wave Script — Audio waveform flowing out of a stylized document. Represents the bridge between spoken and written Kriolu. 3. Bridge Letters — Two letter forms from Kriolu connected by an arch, symbolizing the bridge between tradition and digital tools.
Selected: Concept 1 (Speech Pen)
Generated Master SVG
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="papia-title">
<title id="papia-title">Papia Studio</title>
<style>
.primary { fill: #4355db; }
.accent { fill: none; stroke: #4355db; stroke-width: 2.5; stroke-linecap: round; }
</style>
<!-- Speech bubble — organic rounded shape, not a perfect rectangle -->
<path class="primary" fill-rule="evenodd" d="
M 25,18 C 25,12 75,12 75,18
L 75,55 C 75,61 58,61 52,61
L 45,72 L 42,61
C 36,61 25,61 25,55 Z
M 50,30 L 52.5,37.5 L 60,37.5 L 54,42 L 56.5,50 L 50,45 L 43.5,50 L 46,42 L 40,37.5 L 47.5,37.5 Z
" />
<!-- Pen nib extending from top-right of bubble -->
<line class="accent" x1="68" y1="18" x2="80" y2="6"/>
<line class="accent" x1="78" y1="8" x2="82" y2="4"/>
<!-- Sparkle lines radiating from pen tip -->
<line class="accent" stroke-width="1.5" x1="84" y1="4" x2="88" y2="2" opacity="0.7"/>
<line class="accent" stroke-width="1.5" x1="82" y1="2" x2="84" y2="-2" opacity="0.5"/>
<line class="accent" stroke-width="1.5" x1="86" y1="6" x2="90" y2="6" opacity="0.6"/>
</svg>How the symbolism works: The speech bubble represents Kriolu as a spoken language. The star cutout (via fill-rule="evenodd") represents cultural value and preservation. The pen nib extending from the bubble bridges spoken word to written/digital form. The sparkle lines convey active creation.
Dark-Mode Favicon SVG
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="papia-fav">
<title id="papia-fav">Papia Studio</title>
<style>
:root { color-scheme: light dark; }
.bg { fill: #f5f5f7; }
.fg { fill: #4355db; }
@media (prefers-color-scheme: dark) {
.bg { fill: #1a1a2e; }
.fg { fill: #8b9cf7; }
}
</style>
<rect class="bg" width="100" height="100" rx="12"/>
<path class="fg" fill-rule="evenodd" d="
M 25,20 C 25,14 75,14 75,20
L 75,58 C 75,64 58,64 52,64
L 45,75 L 42,64
C 36,64 25,64 25,58 Z
M 50,32 L 52.5,39.5 L 60,39.5 L 54,44 L 56.5,52 L 50,47 L 43.5,52 L 46,44 L 40,39.5 L 47.5,39.5 Z
" />
</svg>Script Invocation
uv run .../generate_assets.py \
--svg ./master-icon.svg \
--dark-svg ./favicon.svg \
--bg-color "#f5f5f7" \
--name "Papia Studio" \
--theme-color "#4355db" \
--output-dir ./brand-assets---
Example 4: Existing SVG (Skip to Asset Generation)
User Input
/icon-forge --svg ./src/assets/logo.svgWorkflow
Phases 1-3 are skipped. The skill validates the SVG exists, uses it as both master and favicon (no dark mode variant), and runs the generation script:
uv run .../generate_assets.py \
--svg ./src/assets/logo.svg \
--output-dir ./brand-assets \
--name "App"To provide a dark-mode variant: /icon-forge --svg ./logo.svg --dark-svg ./logo-dark.svg
---
Example 5: Design Seed (--base)
User Input
/icon-forge --base ./old-logo.svg organic, depthWorkflow
The skill reads old-logo.svg, analyzes its structure, and presents:
Base SVG Analysis:
Shapes: 2 circles, 1 rect, 1 polygon
Colors: #333333, #0066cc
Style: Geometric — flat, symmetric, sharp edges
Issues: No <title>, no role="img", viewBox is 0 0 512 512Brand Discovery is abbreviated — colors and concept are already known from the base SVG. The user selected Organic preset with depth enabled. The skill presents:
1. Cleaned original — same design with a11y, normalized to 100x100 viewBox, pixel-aligned 2. Organic reinterpretation — same concept but with flowing bezier curves, irregular blobs replacing the circles, radial gradients on key shapes
User picks the organic variant, iterates, then the normal pipeline continues (dark mode, monochrome, asset generation).
---
Example 6: Geometric Preset (Corporate)
User Input
/icon-forge "Vaultix" - fintech security platform, geometric, flatStyle Selection
User chose Geometric preset with flat depth. The skill applies the geometric technique set: <circle>, <rect>, <polygon>, straight path segments, mathematical symmetry.
Concept
Shield Lock — A geometric shield constructed from clean rectangles with a centered keyhole. Two-tone blue palette. Perfect symmetry, sharp edges, rx rounding only on the outer shield.
This preset produces the clean, corporate, mathematical icons that are appropriate for enterprise and fintech brands.
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.9"
# dependencies = ["Pillow>=10.0.0"]
# ///
"""
Generate platform icon assets from a master SVG.
Produces favicon.ico, apple-touch-icon.png, PWA manifest icons,
maskable icons, iOS source icon, manifest.webmanifest, and integration guide.
Supports framework-specific file naming (e.g., --framework nextjs).
Requires one of these system tools for SVG-to-PNG conversion:
- rsvg-convert (brew install librsvg)
- magick / convert (brew install imagemagick)
Usage:
uv run generate_assets.py --svg icon.svg [options]
Examples:
uv run generate_assets.py --svg logo.svg --name "My App" --output-dir ./icons
uv run generate_assets.py --svg logo.svg --dark-svg logo-dark.svg --bg-color "#1a1a2e"
uv run generate_assets.py --svg logo.svg --framework nextjs --output-dir ./brand-assets
"""
import argparse
import json
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from PIL import Image
# ---------------------------------------------------------------------------
# Framework-specific file naming
# ---------------------------------------------------------------------------
FRAMEWORK_FILE_MAP: dict[str, dict[str, str]] = {
"nextjs": {
"favicon.svg": "icon.svg",
"apple-touch-icon.png": "apple-icon.png",
},
}
def resolve_name(canonical: str, framework: str | None) -> str:
"""Return framework-specific filename, or canonical name if no framework set."""
if framework is None:
return canonical
return FRAMEWORK_FILE_MAP.get(framework, {}).get(canonical, canonical)
# ---------------------------------------------------------------------------
# SVG validation
# ---------------------------------------------------------------------------
def validate_svg(svg_path: Path) -> str:
"""Read and validate that the file is a plausible SVG."""
if not svg_path.exists():
print(f"Error: SVG file not found: {svg_path}", file=sys.stderr)
sys.exit(1)
content = svg_path.read_text(encoding="utf-8")
if "<svg" not in content:
print(f"Error: {svg_path} does not appear to be a valid SVG file.", file=sys.stderr)
sys.exit(2)
return content
# ---------------------------------------------------------------------------
# SVG to PNG conversion via system tools
# ---------------------------------------------------------------------------
def find_svg_converter() -> str:
"""Detect available SVG-to-PNG converter. Returns tool name or exits."""
for tool in ("rsvg-convert", "magick", "convert"):
if shutil.which(tool):
return tool
print(
"Error: No SVG-to-PNG converter found.\n"
"Install one of:\n"
" brew install librsvg (recommended — provides rsvg-convert)\n"
" brew install imagemagick (provides magick/convert)\n",
file=sys.stderr,
)
sys.exit(1)
def svg_to_png_file(svg_path: Path, png_path: Path, size: int, tool: str) -> None:
"""Convert SVG to PNG at *size x size* using a system tool."""
if tool == "rsvg-convert":
cmd = [
"rsvg-convert",
"-w", str(size), "-h", str(size),
"--keep-aspect-ratio",
"-o", str(png_path),
str(svg_path),
]
elif tool in ("magick", "convert"):
cmd = [
tool,
"-background", "none",
"-density", "300",
"-resize", f"{size}x{size}",
str(svg_path),
f"PNG32:{png_path}",
]
else:
raise ValueError(f"Unknown converter: {tool}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(
f"{tool} failed (exit {result.returncode}): {result.stderr.strip()}"
)
def svg_to_image(svg_path: Path, size: int, tool: str) -> Image.Image:
"""Render SVG to a Pillow RGBA Image at *size x size*."""
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
tmp_path = Path(tmp.name)
try:
svg_to_png_file(svg_path, tmp_path, size, tool)
return Image.open(tmp_path).convert("RGBA")
finally:
tmp_path.unlink(missing_ok=True)
# ---------------------------------------------------------------------------
# Asset builders
# ---------------------------------------------------------------------------
def create_icon_with_bg(
svg_path: Path, size: int, bg_color: str, tool: str
) -> Image.Image:
"""Render icon on a solid background (no transparency)."""
fg = svg_to_image(svg_path, size, tool)
bg = Image.new("RGBA", (size, size), bg_color)
bg.paste(fg, (0, 0), fg)
return bg.convert("RGB")
def create_maskable_icon(
svg_path: Path, size: int, bg_color: str, tool: str
) -> Image.Image:
"""Render icon centered in the safe zone (central 80%) on a solid background."""
safe_size = int(size * 0.80)
fg = svg_to_image(svg_path, safe_size, tool)
bg = Image.new("RGBA", (size, size), bg_color)
offset = (size - safe_size) // 2
bg.paste(fg, (offset, offset), fg)
return bg.convert("RGB")
def create_favicon_ico(svg_path: Path, out_path: Path, tool: str) -> None:
"""Build a multi-resolution ICO containing 16x16 and 32x32 frames."""
img_32 = svg_to_image(svg_path, 32, tool)
img_16 = svg_to_image(svg_path, 16, tool)
img_32.save(str(out_path), format="ICO", append_images=[img_16])
def create_manifest(
name: str, short_name: str, theme_color: str, bg_color: str
) -> str:
"""Generate manifest.webmanifest JSON content."""
manifest = {
"name": name,
"short_name": short_name,
"icons": [
{"src": "/icon-192.png", "sizes": "192x192", "type": "image/png"},
{"src": "/icon-512.png", "sizes": "512x512", "type": "image/png"},
{
"src": "/icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable",
},
{
"src": "/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable",
},
],
"theme_color": theme_color,
"background_color": bg_color,
"display": "standalone",
}
return json.dumps(manifest, indent=2) + "\n"
def create_html_snippet() -> str:
"""Generate the HTML <link> tags for the document <head>."""
return (
"<!-- Favicon Package -- paste into <head> -->\n"
'<link rel="icon" href="/favicon.ico" sizes="32x32">\n'
'<link rel="icon" href="/favicon.svg" type="image/svg+xml">\n'
'<link rel="apple-touch-icon" href="/apple-touch-icon.png">\n'
'<link rel="manifest" href="/manifest.webmanifest">\n'
)
def create_nextjs_guide() -> str:
"""Generate a Next.js App Router placement guide."""
return (
"# Next.js App Router — Icon Placement Guide\n"
"#\n"
"# Copy to app/ directory (auto-detected by Next.js):\n"
"# favicon.ico → app/favicon.ico\n"
"# icon.svg → app/icon.svg\n"
"# apple-icon.png → app/apple-icon.png\n"
"# manifest.webmanifest → app/manifest.webmanifest\n"
"#\n"
"# Copy to public/ directory (referenced by manifest):\n"
"# icon-192.png → public/icon-192.png\n"
"# icon-512.png → public/icon-512.png\n"
"# icon-maskable-192.png → public/icon-maskable-192.png\n"
"# icon-maskable-512.png → public/icon-maskable-512.png\n"
"#\n"
"# Next.js auto-generates <link> tags from file-based metadata.\n"
"# No manual <link> tags needed in layout.tsx.\n"
"#\n"
"# Reference: https://nextjs.org/docs/app/api-reference/"
"file-conventions/metadata/app-icons\n"
)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate platform icon assets from a master SVG.",
)
parser.add_argument(
"--svg", required=True, type=Path, help="Path to master SVG icon file"
)
parser.add_argument(
"--dark-svg", type=Path, default=None,
help="Path to dark-mode SVG for favicon.svg (defaults to --svg)",
)
parser.add_argument(
"--output-dir", type=Path, default=Path("./brand-assets"),
help="Output directory (default: ./brand-assets)",
)
parser.add_argument(
"--bg-color", default="#ffffff",
help="Background color for apple-touch-icon and maskable icons (default: #ffffff)",
)
parser.add_argument(
"--name", default="App",
help="App name for manifest.webmanifest (default: App)",
)
parser.add_argument(
"--short-name", default=None,
help="Short name for manifest (defaults to --name)",
)
parser.add_argument(
"--theme-color", default=None,
help="Theme color for manifest (defaults to --bg-color)",
)
parser.add_argument(
"--framework", choices=["nextjs"], default=None,
help="Target framework for file naming (auto-detected by skill)",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
short_name = args.short_name or args.name
theme_color = args.theme_color or args.bg_color
framework = args.framework
# Validate inputs
validate_svg(args.svg)
dark_svg_path = args.dark_svg or args.svg
if args.dark_svg:
validate_svg(args.dark_svg)
# Find converter
tool = find_svg_converter()
# Create output directory
try:
args.output_dir.mkdir(parents=True, exist_ok=True)
except OSError as exc:
print(f"Error: Cannot create output directory: {exc}", file=sys.stderr)
return 3
print("Brand Icon Asset Generation")
print("=" * 40)
print(f"Input SVG: {args.svg}")
if args.dark_svg:
print(f"Dark-mode SVG: {args.dark_svg}")
print(f"Background color: {args.bg_color}")
print(f"Converter: {tool}")
if framework:
print(f"Framework: {framework}")
print(f"Output directory: {args.output_dir}/")
print()
print("Generating assets...")
generated = []
try:
# 1. favicon.ico (16x16 + 32x32 via Pillow ICO save)
out = args.output_dir / "favicon.ico"
create_favicon_ico(args.svg, out, tool)
generated.append(("favicon.ico", "16x16 + 32x32", out))
# 2. favicon.svg / icon.svg (dark-mode-aware copy)
svg_name = resolve_name("favicon.svg", framework)
out = args.output_dir / svg_name
shutil.copy2(dark_svg_path, out)
generated.append((svg_name, "vector", out))
# 3. apple-touch-icon.png / apple-icon.png (180x180, solid bg)
apple_name = resolve_name("apple-touch-icon.png", framework)
img = create_icon_with_bg(args.svg, 180, args.bg_color, tool)
out = args.output_dir / apple_name
img.save(str(out), "PNG")
generated.append((apple_name, "180x180", out))
# 4-5. Standard PNG icons (transparent background)
for size in (192, 512):
out = args.output_dir / f"icon-{size}.png"
svg_to_png_file(args.svg, out, size, tool)
generated.append((f"icon-{size}.png", f"{size}x{size}", out))
# 6-7. Maskable icons (80% safe zone, solid bg)
for size in (192, 512):
img = create_maskable_icon(args.svg, size, args.bg_color, tool)
out = args.output_dir / f"icon-maskable-{size}.png"
img.save(str(out), "PNG")
generated.append(
(f"icon-maskable-{size}.png", f"{size}x{size} safe zone", out)
)
# 8. iOS App Store source (1024x1024, solid bg)
img = create_icon_with_bg(args.svg, 1024, args.bg_color, tool)
out = args.output_dir / "icon-1024.png"
img.save(str(out), "PNG")
generated.append(("icon-1024.png", "1024x1024", out))
# 9. Master SVG copy
out = args.output_dir / "master-icon.svg"
shutil.copy2(args.svg, out)
generated.append(("master-icon.svg", "vector", out))
# 10. manifest.webmanifest
manifest = create_manifest(args.name, short_name, theme_color, args.bg_color)
out = args.output_dir / "manifest.webmanifest"
out.write_text(manifest, encoding="utf-8")
generated.append(("manifest.webmanifest", "JSON", out))
# 11. Integration guide (framework-specific or generic HTML snippet)
if framework == "nextjs":
guide = create_nextjs_guide()
out = args.output_dir / "_nextjs-guide.txt"
out.write_text(guide, encoding="utf-8")
generated.append(("_nextjs-guide.txt", "placement guide", out))
else:
snippet = create_html_snippet()
out = args.output_dir / "_html-snippet.html"
out.write_text(snippet, encoding="utf-8")
generated.append(("_html-snippet.html", "HTML", out))
except Exception as exc:
print(f"\nError during asset generation: {exc}", file=sys.stderr)
return 2
# Report
for name, desc, path in generated:
size_kb = path.stat().st_size / 1024
print(f" [OK] {name:<30s} ({desc}) -- {size_kb:.1f} KB")
print(f"\nDone! {len(generated)} files generated in {args.output_dir}/")
return 0
if __name__ == "__main__":
sys.exit(main())
Troubleshooting
---
No SVG-to-PNG Converter Found
Symptoms
Error: No SVG-to-PNG converter found.Cause
The script requires rsvg-convert (from librsvg) or magick/convert (from ImageMagick).
Solution
macOS (recommended):
brew install librsvgAlternative (macOS):
brew install imagemagickUbuntu/Debian:
sudo apt-get install librsvg2-binVerify:
which rsvg-convert # should print a path---
uv Not Installed
Symptoms
uv: command not foundSolution
curl -LsSf https://astral.sh/uv/install.sh | shOr via Homebrew:
brew install uv---
SVG Unrecognizable at Small Sizes
Symptoms
- Icon looks like a blob at 16px or 32px
- Details merge together in the favicon
Cause
The SVG has too much detail, thin strokes, or complex shapes that collapse at small pixel counts.
Solution
1. Simplify the master SVG: fewer paths, thicker strokes (>= 2 units at 100x100) 2. Remove small decorative elements 3. Use solid fills instead of thin outlines 4. Test by viewing the SVG at actual favicon size:
<img src="master-icon.svg" width="16" height="16">
<img src="master-icon.svg" width="32" height="32">---
Apple Touch Icon Has Black Background
Symptoms
- iOS shows black or gray background behind the icon on the home screen
Cause
The apple-touch-icon.png has transparency. iOS fills transparent areas with black.
Solution
Re-run the script with --bg-color set to your brand background color:
uv run generate_assets.py --svg icon.svg --bg-color "#2563eb"The default is #ffffff (white). The script composites the icon onto this solid background.
---
Maskable Icons Clip Content on Android
Symptoms
- Icon edges are cut off when displayed as an adaptive icon on Android
Cause
Content extends outside the safe zone (central 80% of canvas). Different launchers apply different mask shapes (circle, squircle, teardrop).
Solution
The script already pads the icon to 80% of the canvas for maskable variants. If content still clips: 1. Simplify the master SVG to use less of the canvas edges (keep content within 70% of the viewBox) 2. Preview at maskable.app by uploading the generated icon-maskable-512.png
---
Favicon Not Updating in Browser
Symptoms
- Browser shows the old or generic icon after deploying new favicon
Cause
Browsers aggressively cache favicons. A hard refresh may not be enough.
Solution
1. Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac) 2. Clear favicon cache: Navigate directly to /favicon.ico and hard refresh there 3. Add cache buster (temporary): <link rel="icon" href="/favicon.ico?v=2"> 4. Verify MIME types are served correctly:
.ico→image/x-icon.svg→image/svg+xml.webmanifest→application/manifest+json
---
Dark Mode Favicon Not Switching
Symptoms
- SVG favicon stays in light mode even when OS is set to dark theme
Cause
- Browser doesn't fully support SVG favicon media queries
- CSS classes not applied correctly in the SVG
Solution
1. Verify favicon.svg contains @media (prefers-color-scheme: dark) block 2. Ensure the SVG <link> tag uses type="image/svg+xml":
<link rel="icon" href="/favicon.svg" type="image/svg+xml">3. Test in Chrome or Firefox (best support). Safari has partial support. 4. The .ico fallback will always show the light-mode version — this is expected.
---
ImageMagick Renders SVG Poorly
Symptoms
- Generated PNGs look pixelated, misaligned, or have missing elements
- Works fine with
rsvg-convertbut not withmagick/convert
Cause
ImageMagick delegates SVG rendering to its built-in renderer or to Inkscape/librsvg if available. The built-in renderer has limited SVG support.
Solution
Install librsvg instead — it has superior SVG rendering:
brew install librsvgThe script auto-detects rsvg-convert first, falling back to magick only if librsvg is not found.
---
Script Fails with "Invalid SVG"
Symptoms
Error: file.svg does not appear to be a valid SVG file.Cause
The file doesn't contain an <svg tag. It may be:
- A renamed PNG/JPG file
- An SVG wrapped in other XML
- A corrupted download
Solution
1. Open the file in a text editor and verify it starts with <svg or <?xml 2. If it's an <?xml declaration followed by <svg>, it's valid — check for encoding issues 3. If it's a raster image, convert it to SVG first using a vectorization tool
---
Next.js App Router Icons Not Auto-Detected
Symptoms
- Next.js doesn't generate
<link>tags for icons - Browser shows default/no favicon despite files being present in
app/
Cause
Files are not named according to Next.js App Router conventions, or are placed in the wrong directory.
Solution
1. Regenerate with framework flag:
uv run generate_assets.py --svg icon.svg --framework nextjsThis outputs icon.svg (not favicon.svg) and apple-icon.png (not apple-touch-icon.png).
2. Verify file placement:
favicon.ico,icon.svg,apple-icon.png,manifest.webmanifest→app/directory- PWA icons (
icon-192.png,icon-512.png, etc.) →public/directory
3. Check file naming:
| Expected (Next.js) | Common mistake |
|---|---|
icon.svg | favicon.svg |
apple-icon.png | apple-touch-icon.png |
4. Restart dev server — Next.js needs a restart to detect new file-based metadata. 5. Clear `.next` cache — stale cached icons may persist: rm -rf .next && npm run dev
Reference
---
SVG Looks Too Simple / Generic
Symptoms
- Icon looks like basic clip-art or stock shapes
- Output doesn't match the richness of the chosen style preset
- Design feels flat and lifeless despite selecting a non-geometric preset
Cause
The SVG generation is falling back to default geometric primitives instead of applying the style-specific techniques.
Solution
Review against the style-to-SVG technique table in WORKFLOW.md:
- All styles: Are you using cubic bezier
Ccommands (not just straight linesL/H/V)? - Organic: Are bezier control points pulled off-axis to create irregular, non-circular curves?
- Illustrative: Are there at least 3 layered
<g>groups building a scene? Are color blocks distinct and overlapping? - Symbolic: Is there a negative-space element (
fill-rule="evenodd") or dual-meaning composition? - Constellation: Are nodes placed at varied sizes and irregular positions (not a grid)? Are connections curved, not straight?
- Depth enabled: Are
<linearGradient>/<radialGradient>defined in<defs>and referenced viaurl(#id)?
If the output is still generic, regenerate with explicit reference to the SVG Path Techniques section in WORKFLOW.md.
---
Gradients Not Rendering
Symptoms
- SVG shows solid black or no fill where a gradient should be
- Gradient works in some browsers/tools but not others
rsvg-convertormagickproduces flat-colored output
Cause
Common gradient definition issues in SVG.
Solution
1. Check `<defs>` placement — Gradients must be defined inside <defs> within the <svg> element:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ff6b6b"/>
<stop offset="100%" stop-color="#e74c5e"/>
</linearGradient>
</defs>
<rect fill="url(#grad1)" width="100" height="100"/>
</svg>2. Check `url(#id)` reference — The fill or stroke attribute must reference the gradient ID exactly: fill="url(#grad1)". Missing the # or mismatched ID is a common mistake.
3. Avoid ID collisions — If the master SVG and favicon SVG both define id="grad1", they may conflict when embedded in the same page. Use unique prefixes (e.g., id="master-grad1", id="fav-grad1").
4. Prefer `rsvg-convert` — ImageMagick's built-in SVG renderer has limited gradient support. Install librsvg for reliable gradient rendering:
brew install librsvg---
Icons Look Blurry at Small Sizes
Symptoms
- Favicon or 16px/32px renders show anti-aliased blur
- Edges look soft instead of crisp
Cause
Sub-pixel coordinate misalignment. Filled shapes with non-integer coordinates cause the renderer to anti-alias across pixel boundaries.
Solution
1. Ensure filled shape edges use integer coordinates (x="20", not x="20.3") 2. For odd stroke widths (1, 3), offset coordinates by 0.5 (cx="50.5") 3. For even stroke widths (2, 4), use whole-number coordinates 4. Reduce decimal precision to 1-2 places — extra precision adds file size with zero visual benefit at icon scale 5. At the Glyph tier (16-32px), pixel alignment is critical. At Master tier (512+), it matters less
---
SVGO Removes viewBox or Title
Symptoms
- After optimization, SVG no longer scales properly (lost
viewBox) - Screen reader no longer announces the icon (lost
<title>)
Cause
Older SVGO versions (v3 and earlier) had removeViewBox and removeTitle enabled by default.
Solution
Use SVGO v4+ where these are no longer removed by default. If using an older version, explicitly override:
npx svgo --config='{"plugins":[{"name":"preset-default","params":{"overrides":{"removeViewBox":false,"removeTitle":false}}}]}'Detailed Workflow
Complete process for generating brand icons and platform assets.
---
Phase 1: Brand Discovery
Questions to Ask
| Category | Questions |
|---|---|
| Identity | Brand name, tagline, industry/domain |
| Concept | Visual metaphor, symbol, abstract vs literal |
| Colors | Primary brand color (hex), secondary, accent |
| Style | Style preset (Geometric, Organic, Illustrative, Symbolic, Constellation) + grid + depth |
| References | Existing logo, competitor icons, inspiration images |
| Constraints | Must work on dark backgrounds? Existing brand guidelines? |
Style Presets
Present the style menu during discovery. Recommend a preset based on brand description if one fits naturally.
| Preset | Visual Character | Best For |
|---|---|---|
| Geometric | Clean shapes, mathematical precision, flat fills, perfect symmetry | Corporate, fintech, enterprise SaaS |
| Organic | Flowing curves, irregular blobs, natural asymmetry, soft edges | Creative agencies, wellness, community platforms |
| Illustrative | Layered scenes, color blocks, story-driven composition, hand-crafted feel | Education, kids brands, creative tools |
| Symbolic | Dual-meaning line art, negative space tricks, conceptual merging | Studios, consultancies, language/communication tools |
| Constellation | Connected nodes, network graphs, dot clusters, progressive complexity | Dev tools, data platforms, tech networks |
Grid Size
After style selection, confirm the coordinate grid:
- 100×100 (default): More coordinate space for complex brand icons with organic curves, bezier paths, and layered compositions. Recommended for Organic, Illustrative, and Constellation presets.
- 24×24: Industry-standard icon grid (Material Design, Feather, Heroicons). Aligns with the 8px spatial grid. Better for simple, UI-style marks where pixel-perfect alignment matters. Recommended for Geometric and Symbolic presets with simple geometry.
If the user doesn't specify, default to 100×100. The --grid 24 argument selects the 24×24 grid.
Depth Toggle
After style selection, ask about depth preference:
- Flat (default): Solid fills, no gradients. Clean, universal, safest for all renderers.
- Depth: Subtle
<linearGradient>/<radialGradient>, opacity layering, soft shadows via<filter>. Adds warmth and dimension but increases SVG complexity.
Parsing $ARGUMENTS
Extract from the user's arguments:
- Brand name: First quoted string or capitalized word
- Color references: Hex codes (
#2563eb), named colors (blue,coral) - Style keywords: geometric, organic, illustrative, symbolic, constellation, flat, depth
- Grid:
--grid 24for 24×24 viewBox (default: 100×100) - `--svg <path>`: Path to existing SVG — skip to Phase 4 (asset generation only)
- `--base <path>`: Path to existing SVG — use as design seed for Phase 2 iteration
Skip Conditions
- If
--svg <path>provided: validate SVG exists, skip directly to Phase 4 - If
--base <path>provided: validate SVG exists, load it, proceed to Phase 1 (abbreviated) then Phase 2 with design seed workflow - If comprehensive description provided: minimize questions, confirm assumptions
---
Phase 2: SVG Master Design
Design Process
1. Brainstorm 2-3 concepts based on brand info
- Describe each concept in 1-2 sentences before generating SVG
- Present text descriptions first for user direction
- If
--basewas provided, see "Design Seed Workflow" below instead
2. Generate SVG code for chosen concept
- Apply the style preset's SVG technique guidance (see table below)
- Start with the Glyph tier silhouette (what 2-4 shapes survive at 16px?)
- Build up to Mark and Master complexity
3. SVG Structure Requirements
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="icon-title">
<title id="icon-title">Brand Name</title>
<style>
.primary { fill: #2563eb; }
.accent { fill: #1e40af; }
</style>
<circle class="primary" cx="50" cy="50" r="40"/>
</svg>- No
width/heightattributes — CSS/props control size <title>must be the first child element (accessibility)role="img"prevents screen readers from traversing internal elements
4. Validation Checklist
- [ ]
viewBoxis square (0 0 100 100for brand icons, or0 0 24 24for UI-style marks) - [ ] No
width/heightattributes on root<svg> - [ ] No
<text>elements (text doesn't survive 16px rendering) - [ ] No strokes thinner than 2 units in Glyph/Mark tiers (Master may use 1.5+)
- [ ] Color count within preset limit (1-3 most presets; up to 5 for Illustrative/Constellation)
- [ ] All paths are closed (end with
Z) - [ ]
xmlns="http://www.w3.org/2000/svg"attribute present - [ ] No embedded raster images (
<image>) - [ ] No external references (
xlink:hrefto URLs) - [ ]
<title>element as first child of<svg>with brand name - [ ]
role="img"on root<svg>element - [ ] Filled shapes use integer coordinates (no sub-pixel blur)
- [ ] Decimal precision limited to 1-2 places
Style-to-SVG Technique Table
This table is the core creative engine. Each preset maps to specific SVG elements and path strategies:
| Preset | SVG Techniques |
|---|---|
| Geometric | Filled. <circle>, <rect>, <polygon>, straight <path> L/H/V segments, rx/ry rounding, mathematical symmetry |
| Organic | Filled. Cubic bezier C/S commands with off-axis control points, <ellipse> with unequal rx/ry, irregular blob <path> shapes, smooth joins, asymmetric composition |
| Illustrative | Filled. Layered <g> groups for scene composition, flat color blocks as distinct <path> regions, overlapping shapes with opacity, warm multi-color palettes (up to 4-5 colors) |
| Symbolic | Stroked. clip-path and fill-rule="evenodd" for negative space, stroke="currentColor" line art, stroke-width variation, boolean path operations |
| Constellation | Hybrid. Filled <circle> nodes at varied sizes/positions, stroked cubic bezier <path> connections (fill="none"), opacity variation for depth layers, dot clusters |
Depth add-ons (when depth toggle is enabled):
<linearGradient>/<radialGradient>defined in<defs><filter>withfeGaussianBlurfor soft glow or shadow effectsopacitylayering on overlapping elements- Color stops that shift from brand primary to a lighter/darker variant
SVG Path Techniques by Style
Organic blob — Use cubic bezier curves with control points pulled away from the straight path to create irregular, amoeba-like shapes:
<!-- Irregular blob shape — NOT a circle -->
<path d="M 30,50 C 25,30 40,15 55,20 C 70,25 80,40 75,55 C 70,70 55,80 40,75 C 25,70 35,70 30,50 Z" class="primary"/>Constellation connection — Curved paths between node circles, not straight lines:
<!-- Nodes at irregular positions -->
<circle cx="25" cy="60" r="6" class="node"/>
<circle cx="70" cy="35" r="9" class="node-lg"/>
<!-- Curved connection — control points create a natural arc -->
<path d="M 25,60 C 35,40 55,30 70,35" fill="none" stroke-width="2" class="connection"/>Illustrative layering — Overlapping groups build a scene:
<g class="background">
<path d="M 0,60 C 20,50 40,55 60,50 C 80,45 100,55 100,100 L 0,100 Z" class="water"/>
</g>
<g class="midground">
<path d="M 45,25 L 50,60 L 40,60 Z" class="mast"/>
<path d="M 47,25 C 60,30 65,45 55,58 L 50,58 L 50,28 Z" class="sail"/>
</g>Symbolic negative space — fill-rule="evenodd" cuts inner shapes from outer shapes:
<!-- Outer speech bubble with inner cutout forming a star -->
<path fill-rule="evenodd" d="
M 20,15 C 20,10 80,10 80,15 L 80,65 C 80,70 55,70 50,75 L 45,70 C 40,70 20,70 20,65 Z
M 50,30 L 53,40 L 63,40 L 55,46 L 58,56 L 50,50 L 42,56 L 45,46 L 37,40 L 47,40 Z
" class="primary"/>Progressive Detail Tiers
Design with three consumption tiers in mind:
| Tier | Size Range | Complexity | Purpose |
|---|---|---|---|
| Glyph | 16-32px | 2-4 shapes, silhouette-grade | Favicon, browser tab |
| Mark | 48-192px | Full logomark, moderate detail | App icons, PWA, nav bars |
| Master | 512-1024px | Rich detail, gradients, fine curves | App store, hero images, splash |
Design approach: Start at the Glyph tier — what 2-4 shapes capture the brand's essence when everything else is stripped away? This is the icon's skeleton. Then build up through Mark (add secondary shapes, color variation) to Master (add depth, decorative elements, fine bezier detail).
For complex designs (e.g., Constellation with many nodes), optionally produce a separate favicon-glyph.svg with only the core shapes for the smallest sizes.
Pixel Alignment Rules
Sub-pixel misalignment causes blur when SVGs are rasterized. Follow these rules, especially at the Glyph tier:
- Filled shapes: Keep all edges on integer coordinates (
x="20", notx="20.3") - Even stroke widths (2, 4): Align coordinates to whole numbers
- Odd stroke widths (1, 3): Offset by 0.5 so the stroke straddles the pixel center (
cx="50.5") - Decimal precision: Limit to 1-2 decimal places.
d="M 30.12 50.65"is visually identical tod="M 30.123456 50.654321"at icon scale, and reduces file size
At the Master tier (512-1024px), sub-pixel alignment is less critical because each coordinate maps to multiple rendered pixels. Focus alignment effort on the Glyph tier shapes.
Fill vs Stroke Strategy
Choose the right rendering approach per style preset:
| Preset | Strategy | Rationale |
|---|---|---|
| Geometric | Filled paths | Mathematical shapes render crisply as filled regions; stroke scaling at small sizes is unpredictable |
| Organic | Filled paths | Blob shapes are inherently filled regions; strokes would outline them awkwardly |
| Illustrative | Filled paths | Color-block scenes use filled regions by definition |
| Symbolic | Stroked paths | Line art and negative-space designs rely on stroke weight for visual character |
| Constellation | Hybrid | Nodes are filled; connections are stroked with fill="none" |
Why filled paths are the default: Filled paths bake line thickness into geometry, so they scale predictably from 16px to 1024px. Stroked paths scale proportionally — a 2-unit stroke at 100×100 becomes visually different at 16×16 vs 512×512. For Symbolic presets where stroke character matters, use vector-effect: non-scaling-stroke in the CSS if constant stroke width is desired at all sizes.
5. Present to user
- Save SVG to a temporary file and suggest opening in browser to preview
- Describe the design in words alongside the code
- Show how the design simplifies across the three tiers
6. Iterate based on user feedback until satisfied
Design Seed Workflow (--base)
When --base <path> is provided, use the existing SVG as a starting point instead of designing from scratch.
Step 1: Analyze the base SVG
Read the file and extract:
- Shapes used: circles, rects, paths, polygons — identify the current visual language
- Colors: extract all fill/stroke color values
- Structure: is it flat? layered
<g>groups? uses gradients? - Issues: missing
<title>, norole="img", sub-pixel coordinates, hardcoded width/height,<text>elements
Present the analysis to the user with a summary like:
Base SVG Analysis:
Shapes: 3 paths, 2 circles, 1 rect
Colors: #2563eb (primary), #1e40af (accent)
Style: Geometric — flat fills, symmetric layout
Issues: Missing <title>, no role="img", viewBox is 0 0 512 512 (non-standard)Step 2: Abbreviated Brand Discovery
Skip questions that the base SVG already answers (colors, general concept). Only ask about:
- Style preset: Does the user want to keep the current style or shift to a different preset?
- Depth: Add gradients/depth to a currently flat icon?
- Grid: Keep current viewBox or normalize to 100×100 or 24×24?
Step 3: Generate variations
Present the original SVG alongside 2 improved variations: 1. Cleaned original — same design, but with a11y attributes, normalized viewBox, pixel-aligned coordinates, and any issues fixed 2. Style-shifted variant — the same concept redesigned with the chosen style preset's techniques (e.g., organic blobs instead of circles, curved connections instead of straight lines)
Let the user choose which direction to take, then iterate from there.
Step 4: Continue normal pipeline
Once the user approves a design, proceed to Phase 3 (dark mode), Phase 3b (monochrome), Phase 4 (assets), and Phase 5 (integration) as normal.
Output
- Save as
master-icon.svgin the project directory
---
Phase 3: Dark-Mode Favicon Variant
When to Create
Always create a dark-mode variant for the favicon SVG. Modern browsers (Chrome, Firefox, Edge) respect @media (prefers-color-scheme: dark) inside SVG favicons.
Color Adaptation Rules
| Light Mode | Dark Mode Adaptation |
|---|---|
| Dark foreground (#1a1a2e) | Light equivalent (#e0e0ff) |
| Light background (#ffffff) | Dark equivalent (#1a1a2e) |
| Brand primary | Lighter/more saturated version |
| Subtle grays | Inverted or adjusted for dark bg |
Contrast Requirements
All foreground-to-background color pairs must meet WCAG 2.1 AA contrast ratio >= 4.5:1.
Template
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="fav-title">
<title id="fav-title">Brand Name</title>
<style>
:root { color-scheme: light dark; }
.bg { fill: #ffffff; }
.fg { fill: #1a1a2e; }
.brand { fill: #2563eb; }
@media (prefers-color-scheme: dark) {
.bg { fill: #1a1a2e; }
.fg { fill: #e0e0ff; }
.brand { fill: #60a5fa; }
}
</style>
<rect class="bg" width="100" height="100" rx="12"/>
<path class="fg" d="..."/>
<circle class="brand" cx="50" cy="50" r="20"/>
</svg>Output
- Save as
favicon.svgin the project directory
---
Phase 3b: Monochrome Variant
Purpose
Generate a currentColor monochrome variant of the master SVG. This enables CSS-based theming — the icon inherits color from its parent element, making it usable in navbars, footers, documentation, and any context where brand colors aren't appropriate.
Process
1. Duplicate the master SVG 2. Replace all fill and stroke color values with currentColor 3. Remove <style> blocks, gradients, and filters — the icon should be a single-color silhouette 4. Keep the <title> and role="img" accessibility attributes
Template
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="mono-title">
<title id="mono-title">Brand Name</title>
<path fill="currentColor" d="..."/>
</svg>Output
- Save as
monochrome.svgin the project directory - Pass to generate_assets.py via
--mono-svgif the script supports it, otherwise include in the output directory manually
---
Phase 4: Asset Generation
Prerequisites
1. Master SVG file exists (from Phase 2 or --svg argument) 2. One of these system tools is installed:
rsvg-convert(recommended):brew install librsvgmagick/convert:brew install imagemagick
3. uv is available for PEP 723 script execution
Framework Detection
Before running the script, detect the target project's framework:
1. If next.config.(js|mjs|ts) exists AND app/layout.(tsx|jsx|js) exists → Next.js App Router. Add --framework nextjs to the script command. 2. Otherwise → omit --framework (generic output with favicon.svg, apple-touch-icon.png).
Script Invocation
uv run <absolute-path-to>/skills/icon-forge/scripts/generate_assets.py \
--svg ./master-icon.svg \
--dark-svg ./favicon.svg \
--bg-color "#ffffff" \
--name "App Name" \
--short-name "App" \
--theme-color "#2563eb" \
--framework nextjs \
--output-dir ./brand-assetsCLI Arguments
| Argument | Required | Default | Description |
|---|---|---|---|
--svg | Yes | — | Path to master SVG icon |
--dark-svg | No | Same as --svg | Dark-mode SVG for favicon.svg |
--output-dir | No | ./brand-assets | Output directory |
--bg-color | No | #ffffff | Background for apple-touch-icon and maskable icons |
--name | No | App | App name in manifest |
--short-name | No | Same as --name | Short name in manifest |
--theme-color | No | Same as --bg-color | Theme color in manifest |
--framework | No | None | Target framework for file naming (auto-detected by skill) |
Generated Files
| File | Size | Format | Purpose |
|---|---|---|---|
favicon.ico | 16x16 + 32x32 | ICO | Legacy browsers, Windows taskbar |
favicon.svg | Vector | SVG | Modern browsers with dark mode support |
apple-touch-icon.png | 180x180 | PNG (RGB, solid bg) | iOS/iPadOS Add to Home Screen |
icon-192.png | 192x192 | PNG (RGBA) | Android Chrome home screen |
icon-512.png | 512x512 | PNG (RGBA) | PWA splash screen |
icon-maskable-192.png | 192x192 | PNG (RGB, 80% safe zone) | Android adaptive icon |
icon-maskable-512.png | 512x512 | PNG (RGB, 80% safe zone) | Android adaptive icon |
icon-1024.png | 1024x1024 | PNG (RGB, solid bg) | iOS App Store source |
master-icon.svg | Vector | SVG | Preserved copy of original |
manifest.webmanifest | — | JSON | PWA icon manifest |
_html-snippet.html | — | HTML | Ready-to-paste <link> tags |
With `--framework nextjs`:favicon.svg→icon.svg,apple-touch-icon.png→apple-icon.png,_html-snippet.html→_nextjs-guide.txt(App Router placement instructions).
Optional: SVG Optimization
Before asset generation, optimize the master SVG to reduce file size (typically 50-80% reduction). This is optional but recommended for production use.
SVGO (Node.js, recommended):
npx svgo --multipass master-icon.svg -o master-icon.svg \
--config='{"plugins":[{"name":"preset-default","params":{"overrides":{"removeViewBox":false,"removeTitle":false}}},"sortAttrs"]}'scour (Python):
pip install scour && scour -i master-icon.svg -o master-icon.svg \
--set-precision=2 --enable-viewboxing --enable-comment-stripping \
--shorten-ids --remove-metadataKey optimization rules:
- Never remove `viewBox` — it is what makes SVGs scalable
- Never remove `<title>` — it provides accessibility
- Reduce decimal precision to 2 places for 100×100 viewBox, 1 place for 24×24
- Strip editor metadata, empty
<defs>, identity transforms, and namespace attributes
Verifying Output
After generation, verify:
- [ ] All 11 files exist in output directory
- [ ]
apple-touch-icon.png(orapple-icon.pngwith--framework nextjs) has NO transparency (RGB mode, not RGBA) - [ ]
icon-maskable-*.pnghave content centered within the inner 80% - [ ]
favicon.icofile size > 300 bytes (confirms multiple resolutions) - [ ]
manifest.webmanifestis valid JSON with correct icon entries
---
Phase 5: Integration Output
HTML Snippet
Present the content of _html-snippet.html:
<!-- Favicon Package -- paste into <head> -->
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/manifest.webmanifest">Framework-Specific Placement
| Framework | Icon files location | Manifest location | Notes |
|---|---|---|---|
| Next.js (App Router) | favicon.ico, icon.svg, apple-icon.png → app/; PWA icons → public/ | app/manifest.webmanifest | Auto-detected: --framework nextjs |
| Next.js (Pages) | public/ directory | public/manifest.webmanifest | Default naming works |
| Vite / Vue / React | public/ directory | public/manifest.webmanifest | Default naming works |
| CRA | public/ directory | public/manifest.json (rename) | Default naming works |
| Astro | public/ directory | public/manifest.webmanifest | Default naming works |
| Static HTML | Site root | Site root | Default naming works |
Next.js App Router Integration
When generated with --framework nextjs, place files as follows:
your-nextjs-app/
├── app/
│ ├── favicon.ico ← from brand-assets/
│ ├── icon.svg ← from brand-assets/ (auto-detected by Next.js)
│ ├── apple-icon.png ← from brand-assets/ (auto-detected by Next.js)
│ ├── manifest.webmanifest ← from brand-assets/
│ └── layout.tsx (no <link> tags needed)
├── public/
│ ├── icon-192.png ← from brand-assets/ (referenced by manifest)
│ ├── icon-512.png ← from brand-assets/
│ ├── icon-maskable-192.png ← from brand-assets/
│ └── icon-maskable-512.png ← from brand-assets/Next.js auto-generates <link> tags from the file-based metadata — no manual tags or metadata export needed in layout.tsx.
Summary Report
Present a summary like:
Brand Icon Generation Complete
================================
Brand: [name]
Master SVG: [path]
Favicon SVG: [path] (with dark mode)
Monochrome SVG: [path] (currentColor variant)
Assets directory: [path]
Files generated: 11 + monochrome.svg
Favicon package:
- favicon.ico (16x16 + 32x32)
- favicon.svg / icon.svg (with dark mode CSS)
- apple-touch-icon.png / apple-icon.png (180x180)
PWA icons:
- icon-192.png, icon-512.png
- icon-maskable-192.png, icon-maskable-512.png
Mobile:
- icon-1024.png (iOS App Store source)
Integration:
- manifest.webmanifest
- _html-snippet.html or _nextjs-guide.txt