svg-icon-render
Skill0 starsChanged 5 months agoMITView on GitHub
Render a flat SVG icon from a free icon pack (Lucide, Heroicons, Tabler, Feather, Bootstrap, Phosphor, Material Symbols) to a PNG at any size and color, using macOS qlmanage + PIL. Use when the user says "render an icon", "make an SVG icon", "convert SVG to PNG", "icon for the stream deck", "render Lucide / Heroicons / Tabler / Feather / Phosphor / Material Symbols icon", "give me an icon in <color>", or describes a utility/primitive icon (arrow, refresh, camera, music, settings, etc.) they want as a flat PNG asset. Icon-only — for full Stream Deck button updates see `streamdeck-grid-update`. For AI-illustrated or brand iOS-style icons see `gemini-icon`.
In this repository18 other files it gives its agents.
- app-icon-gridSkill
- archive-manualSkill
- commitSkill
- component-consistencySkill
- component-locatorSkill
- debug-triageSkill
- gemini-iconSkill
- parity-auditorSkill
- prd-writingSkill
- refactor-and-safety-passSkill
- release-helperSkill
- smellSkill
- streamdeck-aerospace-rewireSkill
- streamdeck-workspace-buttonSkill
- test-conventionsSkill
- verification-gateSkill
- work-breakdownSkill
- yt-audioSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 118
All seven packs are free for commercial use.
- Very specific: line 140
Stroke width is set on the SVG, not via CLI: If you want a heavier Lucide stroke, edit stroke-width="2" → stroke-width="2.6" in the SVG …
- Very specific: line 146
qlmanage caches: If you re-render the same filename and get the previous result, append a unique suffix to the SVG path or use a fresh temp …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
SVG icon → PNG render
Renders a single icon from one of seven bundled SVG icon packs to a flat PNG at any size, any color, on any background (or transparent). Built around macOS-native qlmanage so there's nothing to install — qlmanage's WebKit renderer handles modern SVG features (currentColor, clip-path, gradients) better than the Python-native libs.
When to use this vs. other icon skills
- Multi-button Stream Deck change that needs icons →
streamdeck-grid-update. It owns the manifest edit AND calls this skill (or its inline PIL recipes) for the icon layer. - Branded / iOS-style / illustrated / "designed" icon →
gemini-icon. AI-generated, fits product/brand identity. This skill is for utility primitives (arrows, refresh, system glyphs). - Single workspace launcher button →
streamdeck-workspace-button. End-to-end; pulls icons via brand favicon, not this skill. - Anything else — "I need an icon at 128×128 white-on-transparent of a refresh arrow" → this skill.
The pipeline (why three steps)
SVG pack file → force_color() → qlmanage thumbnail → PIL alpha-key + tint → PNGforce_color(svg, "#000000")rewritescurrentColorto literal black and stripswidth=/height=from the root<svg>(otherwise qlmanage honors the pack's 24×24 / 16×16 / 256×256 viewport literally — see Pitfalls). Black is chosen for maximum contrast against qlmanage's white background; the final tint is applied later.qlmanage -t -s <size> -o <dir> <svg>renders to PNG at the requested size. WebKit-backed; handles every pack tested.- PIL alpha-key + tint — convert the black-on-white render into the requested color: alpha =
255 - luminance, RGB = tint color. Replaces the dropped-near-white-pixels trick from/tmp/sd-icons-v2.pyso the same pipeline works for any color including pure white (which would otherwise erase itself).
render.py in this skill folder implements the whole thing. Copy or import it. Smoke-test: python3 render.py /tmp/out.png renders the bundled Lucide arrow-right as white-on-dark 288×288.
Calling the renderer
from render import render
from pathlib import Path
# Stream Deck convention (dark utility background, 288 px)
render(
svg_source=Path.home() / ".claude/skills/svg-icon-render/samples/lucide/arrow-right.svg",
out_path=Path.home() / ".config/aerospace/icons/move-right.png",
color="#3b82f6", # tailwind blue-500
size=288,
background=(26, 26, 26, 255), # #1a1a1a → utility-icon background
)
# Transparent PNG (for layered design / icon library)
render("path/to/icon.svg", Path("/tmp/icon.png"),
color="#111827", size=512, background=None)
# Raw SVG string (no file on disk)
render("<svg viewBox='0 0 24 24'><path d='M5 12h14' stroke='currentColor' stroke-width='2'/></svg>",
Path("/tmp/x.png"), color="#fff")Parameters:
color— hex (#fffor#ffffff). The icon's tint.size— output px (square). 288 = Stream Deck. 512 = generic app icon. 64 = small UI.background— RGBA tuple orNonefor transparent.(26, 26, 26, 255)is the Stream Deck utility convention.pad_ratio— default0.10(10% inset all around). Set lower (e.g. 0.02) when the source SVG already has whitespace baked in.white_threshold— default240. Pixels with mean luminance above this become fully transparent. Lower if your icon contains intentional off-white tones.
Picking a pack — style guide
| Pack | Style | When to reach for it |
| Lucide | 2px stroke, square caps, 24×24 | Default for utility/system icons. Already used on Tarik's Stream Deck (move-*, join-*, reset, flip). Outlined, abstract, neutral. |
| Feather | 2px stroke, 24×24 | Lucide's parent project — basically the same look with a smaller catalog. Use if Lucide doesn't have a glyph; otherwise prefer Lucide (actively maintained). |
| Tabler | 1.5px stroke, 24×24 | Largest free outline catalog (4500+). Slightly thinner stroke than Lucide; reach for it when Lucide is missing a niche glyph. |
| Heroicons (outline) | 1.5px stroke, 24×24, "Apple-y" rounded | Tailwind makers'. Slightly softer look than Lucide. Pair with Tailwind UIs. |
| Heroicons (solid) | Filled, no stroke, 24×24 | iOS-style filled glyphs. Use for status pills, badge icons, "weight in the corner" UIs — feels heavier than outline. |
| Bootstrap Icons | Filled, mostly fill-only, 16×16 | Bootstrap-native. Tiny viewBox (renders fine — viewBox scales) but lower visual density than the 24×24 packs at small sizes. |
| Phosphor | 256×256, six weights | Distinctive friendly rounded corners. Six weights (thin/light/regular/bold/duotone/fill). Use when the design needs personality. Each weight is a separate npm package; this skill bundles regular and fill. |
| Material Symbols | 48×48 (or icon font), three styles | Google's. Recognizable as "Google design." Three styles (outlined/rounded/sharp). Best when matching Google product surfaces or Android-feeling UIs. Apache 2.0 licensed (vs ISC/MIT for the others). |
Heuristic: for Tarik's Stream Deck system glyphs, default to Lucide. For "this should feel like an iOS button," reach for Heroicons solid or Phosphor fill. For breadth ("does anyone have an icon for X?"), check Tabler first.
Bundled samples
Under samples/<pack>/ — 5 SVGs per pack (arrow, refresh, camera, music, settings) so future agents have a reference without network. Pack-by-pack arrow filename (catalog conventions differ):
lucide/arrow-right.svgheroicons-outline/arrow-right.svg(alsoarrow-path= refresh)heroicons-solid/arrow-right.svgtabler/arrow-right.svgfeather/arrow-right.svgbootstrap/arrow-right.svg(refresh isarrow-clockwise, settings isgear)phosphor-regular/arrow-right.svg(refresh isarrow-clockwise)phosphor-fill/arrow-right.svgmaterial-symbols-outlined/arrow_forward.svg(Google uses snake_case, refresh isrefresh, camera isphoto_camera)
To fetch additional icons from a pack on demand:
# Lucide — every icon name is kebab-case
curl -sSfL "https://cdn.jsdelivr.net/npm/lucide-static/icons/zap.svg" -o samples/lucide/zap.svg
# Heroicons — /24/outline/ or /24/solid/
curl -sSfL "https://cdn.jsdelivr.net/npm/heroicons/24/outline/bell.svg" -o /tmp/bell.svg
# Tabler — /outline/ or /filled/
curl -sSfL "https://cdn.jsdelivr.net/npm/@tabler/icons/icons/outline/zap.svg" -o /tmp/zap.svg
# Feather
curl -sSfL "https://cdn.jsdelivr.net/npm/feather-icons/dist/icons/zap.svg" -o /tmp/zap.svg
# Bootstrap Icons
curl -sSfL "https://cdn.jsdelivr.net/npm/bootstrap-icons/icons/lightning.svg" -o /tmp/lightning.svg
# Phosphor — regular | thin | light | bold | duotone | fill (fill names end in -fill.svg)
curl -sSfL "https://cdn.jsdelivr.net/npm/@phosphor-icons/core/assets/regular/lightning.svg" -o /tmp/lightning.svg
curl -sSfL "https://cdn.jsdelivr.net/npm/@phosphor-icons/core/assets/fill/lightning-fill.svg" -o /tmp/lightning.svg
# Material Symbols — outlined | rounded | sharp; names are snake_case
curl -sSfL "https://cdn.jsdelivr.net/npm/@material-symbols/svg-400/outlined/bolt.svg" -o /tmp/bolt.svgLicenses (per pack — verify before redistributing)
All seven packs are free for commercial use. License terms differ:
- Lucide — ISC. Attribution not required. Bundled
lucide-static. - Heroicons — MIT. Attribution not required. Tailwind Labs.
- Tabler Icons — MIT.
- Feather Icons — MIT.
- Bootstrap Icons — MIT.
- Phosphor Icons — MIT.
- Material Symbols — Apache 2.0. Attribution preserved in package header.
Redistributing 5 sample SVGs from each pack inside this skill is fine under all of the above. If shipping a product that bundles the full pack, re-check the upstream LICENSE file (most use the package.json#license field).
Pitfalls (caught while building this skill)
- Forcing
fill="<color>"on a stroke-only SVG fills closed paths solid. Lucide arrow-right looks like a filled triangle blob if you setfill="#fff"on its root. Solution inrender.py: only substitutecurrentColor, don't override the root's stroke-vs-fill choice. The pack's authors already made that call. - qlmanage honors
width="24"literally —-s 288only sets the canvas, not the render scale. If the SVG haswidth="24" height="24"attributes, you get a 24-pixel icon centered in 288 pixels of whitespace. Strip them before rendering (render.pydoes this). TheviewBoxalone tells WebKit how to scale. - Pure-white tint erases itself. The original
/tmp/sd-icons-v2.pyworked because it used vivid stroke colors (blue, green, amber, pink) far from white, so dropping near-white pixels left only the colored strokes. Trying the same withcolor="#ffffff"deletes the icon. Solution: render in BLACK on white, then alpha-key (luminance → alpha) and tint to the target color in PIL.render()always does this regardless of the requested color. - Multi-color SVGs (favicons, illustrations) need different handling. This pipeline assumes flat single-color icons. If the source SVG has multiple
fill="…"colors (rare in icon packs, common in favicons), it'll all collapse to the tint color. For brand favicons → seestreamdeck-grid-updateoperation H's "Branded icons" recipe (LANCZOS resize, no recolor). For illustrated icons →gemini-icon. - Stroke width is set on the SVG, not via CLI. If you want a heavier Lucide stroke, edit
stroke-width="2"→stroke-width="2.6"in the SVG before rendering. There's no CLI flag for this. - Bootstrap Icons' viewBox is
0 0 16 16, not 24. No-op for the renderer (viewBox is just scale), but the visual weight is heavier — you may want a smallerpad_ratio(e.g. 0.05) since the icon already uses more of the box. - Material Symbols' viewBox is
0 -960 960 960(negative Y origin). Looks weird but renders correctly — WebKit handles it. - qlmanage caches. If you re-render the same filename and get the previous result, append a unique suffix to the SVG path or use a fresh temp dir (
render.pyusestempfile.TemporaryDirectoryso each call is clean).
Cross-references
streamdeck-grid-update— Stream Deck manifest edits. Calls into this skill for icon work. Owns theImages/copy and the on-disk-side wiring.gemini-icon— AI-generated branded/illustrated icons. Use when "draw me a custom icon" rather than "render this glyph from a free pack."streamdeck-workspace-button— single workspace launcher button (uses brand favicons, not pack icons).- Reference render:
/tmp/sd-icons-v2.pyis the original Stream Deck batch that established the qlmanage-based pipeline; this skill generalized it (any size/color/bg, render-then-tint instead of drop-near-white, multiple packs instead of inline Lucide path strings).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

