SVG Logo Design skill for Codex
Marks that hold up at 16px and on a billboard. Codex designs a logo the way a brand designer would: brief first, several distinct concepts compared at real sizes, construction on a grid, one-color approval before color, and optical corrections for small sizes. Lettering is outlined from the font with fontTools, and a bundled audit script rejects missing viewBoxes, live text and embedded rasters before the favicon and app-icon set is exported.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- svg-logo-design Installs to ~/.agents/skills/svg-logo-design/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install svg-logo-design The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project svg-logo-design Installs to .agents/skills/svg-logo-design/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/svg-logo-design.zipunzip -o svg-logo-design.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) svg-logo-design PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $svg-logo-design, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Designs logos, symbols and wordmarks as hand-built SVG - concept from the brief, construction on a grid, monochrome first, optical corrections, outlined lettering via fontTools, lockup variants, and an export set of favicon.svg, ICO, apple-touch and maskable PWA icons. Includes an audit script for viewBox, raster, live text and stroke problems. Use when asked to create or refine a logo, mark, monogram or wordmark, clean up a messy logo SVG, or produce favicons and app icons from a mark.
Use it when
- A product needs a first logo, symbol or monogram
- An existing logo SVG is bloated, uses live text or embeds a PNG
- You need favicon.svg, favicon.ico, apple-touch and maskable icons from a mark
- A wordmark must be converted to paths with license-safe lettering
Use something else when
- Designing a UI icon set with consistent strokes and sprites: use icon-systems
- Writing the full brand guideline document around a finished logo: use brand-guidelines
How to check the result
svg_audit.py reports no errors on every exported SVG, and the symbol is legible when rendered at 16px and 32px on both light and dark backgrounds.
The mistake it prevents
Approving a mark at large size in full color, then discovering it turns into a smudge as a 16px favicon or in one-color print.
A finished mark reads at 16px and at billboard size, works in one flat color, has a reason behind its shape that the client can repeat in one sentence, and ships as clean, outlined, optimised SVG plus the raster sizes platforms require.
Workflow
- Write the brief back in five lines before drawing anything.
- Generate 3-5 distinct concepts as black-on-white thumbnails at 32px and 256px.
- Pick one with the user; refine construction on a grid.
- Apply optical corrections; test at 16, 32, 64 px and reversed.
- Add color, then build lockups (symbol, wordmark, horizontal, stacked).
- Optimise, run
scripts/svg_audit.py, export the icon set.
1. Brief
Ask for or infer, then confirm:
Name: <exact spelling and casing>
What they do: <one sentence>
Audience and feel: <three adjectives, and one thing it must NOT feel like>
Where it lives: <app icon, favicon, product UI, print, signage, merch>
Constraints: <existing colors, competitor marks to avoid, must-include element>
Look at existing assets in the repo first (rg --files -g '*.svg' | rg -i 'logo|brand|mark')
and read AGENTS.md for where assets belong.
2. Concepts
Choose a mark type deliberately:
| Type | Use when | Risk |
|---|---|---|
| Wordmark (lettering only) | Name is short, distinctive, and must be learned | Weak as an app icon; needs a monogram fallback |
| Monogram / lettermark | Long name, needs a compact icon | Generic unless the letterform is modified |
| Abstract symbol | Product spans many offerings; long-term brand | Means nothing until the brand earns it |
| Pictorial symbol | A concrete idea is central (a leaf for a garden app) | Clichés: check it is not the first stock icon result |
| Combination mark | Most startups: symbol for icons, wordmark for headers | Two things to keep balanced |
Each concept gets a one-line rationale tied to the brief. Render them side by side in a scratch HTML page so the user can compare at real sizes:
<div style="display:flex;gap:48px;align-items:end;font:14px system-ui">
<figure><img src="c1.svg" width="256"><img src="c1.svg" width="32"><img src="c1.svg" width="16">
<figcaption>C1 - folded path: "two teams meeting"</figcaption></figure>
</div>
Never trace or closely imitate an existing trademark. If the user asks for “like the X logo”, take the quality (geometric, playful), not the shape.
3. Construction
- Work in a
viewBox="0 0 64 64"(symbol) or a height-normalised box for wordmarks. Integer and half-integer coordinates keep edges crisp at small sizes. - Build from geometry: circles, arcs, 45-degree and 90-degree segments, a consistent
corner radius. Write path data by hand or compute it; keep a construction layer
(circles, guides) in a separate
-construction.svgfor the guideline document. - Prefer filled shapes to strokes. If you drew with strokes, outline them before export (Inkscape: Path > Stroke to Path; or redraw the stroke as two offset paths).
- Use
fill-rule="evenodd"or separate subpaths for counters; check holes render as holes.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<title>Northfold</title>
<path fill="currentColor" fill-rule="evenodd"
d="M8 8h28a20 20 0 0 1 20 20v28H28A20 20 0 0 1 8 36Zm12 12v16a8 8 0 0 0 8 8h16V28a8 8 0 0 0-8-8Z"/>
</svg>
fill="currentColor" lets the mark inherit text color inline; the exported brand files
use explicit hex fills.
4. Optical corrections
| Problem | Correction |
|---|---|
| Circle looks smaller than square of same height | Overshoot round shapes 2-3% beyond the baseline and cap line |
| Horizontal strokes look heavier than vertical | Make horizontals 5-10% thinner |
| Mark looks left-heavy or low when centred | Shift the visual centre up about 2-4% of height |
| Counters close up at 16px | Open counters and gaps to at least 1/16 of the viewBox |
| Reversed (white on dark) looks bolder | Provide a reversed variant 2-4% lighter in weight, or accept and document |
Test by rendering at 16, 32, 64px in a browser, both on white and on near-black.
5. Lettering
Wordmarks must be outlined: live <text> depends on fonts the viewer may not have.
Check the font licence permits logo use (most OFL fonts do; many commercial licences
need a specific tier). To outline with fontTools (pip install fonttools, needs network):
from fontTools.ttLib import TTFont
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.pens.transformPen import TransformPen
font = TTFont("Fraunces-SemiBold.ttf") # static instance, not the variable file
glyphs, cmap = font.getGlyphSet(), font.getBestCmap()
ascent = font["hhea"].ascent
x, tracking, parts = 0, 20, [] # tracking in font units
for ch in "northfold":
name = cmap[ord(ch)]
pen = SVGPathPen(glyphs)
glyphs[name].draw(TransformPen(pen, (1, 0, 0, -1, x, ascent))) # flip y-up to y-down
parts.append(pen.getCommands())
x += glyphs[name].width + tracking
print(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {x - tracking} {ascent - font["hhea"].descent}">'
f'<path d="{" ".join(parts)}"/></svg>')
This ignores kerning. Kern pairs by eye afterwards (adjust x per pair), then modify
letterforms so the wordmark is not just a typed font: a cut terminal, a joined pair, a
custom counter.
6. Color and variants
Design and approve in one color first. Then add at most 1-3 colors and produce:
| File | Content |
|---|---|
logo-symbol.svg | Symbol only, square viewBox |
logo-horizontal.svg | Symbol left, wordmark right, gap = 0.5x symbol width |
logo-stacked.svg | Symbol above wordmark |
logo-wordmark.svg | Wordmark only |
*-mono-black.svg, *-mono-white.svg | One-color versions of each |
Define clear space (for example, the height of one counter) and minimum size (the size
at which the counters still open, usually 16-24px for the symbol, 80-120px wide for a
horizontal lockup) and record both for $brand-guidelines.
Export set
npx svgo logo-symbol.svg -o dist/logo-symbol.svg --multipass --precision 2 # network on first run
python3 scripts/svg_audit.py dist/*.svg
rsvg-convert -w 180 -h 180 icon-padded.svg -o dist/apple-touch-icon.png
rsvg-convert -w 512 -h 512 icon-maskable.svg -o dist/icon-maskable-512.png
magick dist/icon-16.png dist/icon-32.png dist/icon-48.png dist/favicon.ico
rsvg-convert (librsvg) and magick (ImageMagick 7) may need installing; Inkscape’s
inkscape in.svg --export-type=png --export-width=512 --export-filename=out.png works
too. A favicon SVG can follow the color scheme:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<style>path{fill:#0f172a}@media (prefers-color-scheme:dark){path{fill:#f8fafc}}</style>
<path d="..."/>
</svg>
<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">
Apple touch icons need an opaque background (transparent areas render black). Maskable icons keep the mark inside the central circle of 80% diameter; platforms crop the rest.
Audit script
python3 scripts/svg_audit.py dist/logo-symbol.svg dist/logo-horizontal.svg
Errors (exit 1): no viewBox, embedded raster, live <text>, external refs, scripts.
Warnings: editor metadata, strokes, more than 4 colors, over-precise coordinates, no
<title>, files over 15 KB.
Gotchas
- SVGO with
removeViewBoxenabled (default in older SVGO versions) breaks scaling; confirmviewBoxsurvives optimisation. - Gradients and shadows vanish in one-color uses (embroidery, fax, laser etching). The mark must stand without them.
- A mark that only works at 256px fails as a favicon. Simplify, or draw a separate small-size version and document when to use it.
- Trademark clearance is a legal question. Say plainly that a trademark lawyer should search before the client invests in the mark.
Deliverable summary
## Logo delivery: <name>
Concept: <one-sentence rationale>
Files: dist/ (symbol, horizontal, stacked, wordmark; color, mono-black, mono-white)
Icons: favicon.svg, favicon.ico (16/32/48), apple-touch-icon.png (180), icon-192/512, maskable-512
Colors: <hex values> Font: <name, licence>
Clear space: <rule> Minimum size: <px>
Audit: svg_audit.py clean on all SVGs
Open: trademark search not performed What the zip contains
Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "SVG Logo Design"
short_description: "Marks that hold up at 16px and on a billboard"
default_prompt: "$svg-logo-design Design a simple SVG logo for my note-taking app called Northfold" scripts/svg_audit.py
#!/usr/bin/env python3
"""Audit a logo or mark SVG for production readiness.
Usage:
python3 scripts/svg_audit.py logo.svg [more.svg ...]
Errors (exit 1): unparsable XML, missing viewBox, embedded raster <image>,
live <text> (fonts not outlined), external references.
Warnings: editor metadata, strokes (scale with the mark), too many colors,
over-precise coordinates, missing <title>, large file size, fixed width/height only.
Standard library only.
"""
import re
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
SVG_NS = "http://www.w3.org/2000/svg"
EDITOR_NS = ("inkscape", "sodipodi", "sketch", "figma", "illustrator", "adobe")
NUM = re.compile(r"-?\d*\.\d+|-?\d+")
COLOR = re.compile(r"#[0-9a-fA-F]{3,8}\b|rgba?\([^)]*\)|oklch\([^)]*\)")
def local(tag):
return tag.split("}", 1)[-1] if "}" in tag else tag
def audit(path):
errors, warns = [], []
raw = Path(path).read_text(encoding="utf-8")
size = len(raw.encode("utf-8"))
try:
root = ET.fromstring(raw)
except ET.ParseError as exc:
return [f"XML parse error: {exc}"], [], {}
if local(root.tag) != "svg":
errors.append("root element is not <svg>")
vb = root.get("viewBox")
if not vb:
errors.append("missing viewBox (the mark will not scale)")
else:
parts = NUM.findall(vb)
if len(parts) == 4 and parts[:2] != ["0", "0"]:
warns.append(f"viewBox origin is {parts[0]},{parts[1]}; normalise to 0 0 for predictable placement")
if root.get("width") and root.get("height") and not vb:
errors.append("fixed width/height without viewBox")
tags, colors, strokes, decimals = {}, set(), 0, 0
for el in root.iter():
name = local(el.tag)
tags[name] = tags.get(name, 0) + 1
for k, v in el.attrib.items():
if any(ns in k.lower() for ns in EDITOR_NS):
warns.append(f"editor attribute {local(k)} on <{name}>")
if local(k) in ("href",) and v and not v.startswith("#"):
errors.append(f"external reference {v[:60]} on <{name}>")
if local(k) in ("fill", "stroke", "stop-color", "style"):
colors.update(c.lower() for c in COLOR.findall(v))
if local(k) == "stroke" and v not in ("none", ""):
strokes += 1
if local(k) in ("d", "points", "transform"):
decimals += sum(1 for n in NUM.findall(v) if "." in n and len(n.split(".")[1]) > 2)
if name == "style" and el.text:
colors.update(c.lower() for c in COLOR.findall(el.text))
if "stroke:" in el.text and "stroke:none" not in el.text.replace(" ", ""):
strokes += 1
if tags.get("image"):
errors.append(f"{tags['image']} embedded <image> element(s); logos must be pure vector")
if tags.get("text") or tags.get("tspan"):
errors.append("live <text> found; convert lettering to paths so it renders without the font")
if tags.get("foreignObject"):
errors.append("<foreignObject> is not allowed in a logo")
if tags.get("script"):
errors.append("<script> is not allowed in a logo")
if tags.get("metadata") or "<!-- Generator" in raw:
warns.append("editor metadata or generator comment present; run SVGO")
if not tags.get("title"):
warns.append("no <title>; add one (or set aria-label where the SVG is inlined)")
if strokes:
warns.append(f"{strokes} stroke declaration(s); outline strokes so weight scales with the mark")
if len(colors) > 4:
warns.append(f"{len(colors)} distinct colors; most marks use 1-3")
if decimals > 20:
warns.append(f"{decimals} coordinates with >2 decimals; round to 1-2 places")
if size > 15_000:
warns.append(f"file is {size/1024:.1f} KB; a mark is usually under 5 KB after optimisation")
stats = {"bytes": size, "colors": sorted(colors), "paths": tags.get("path", 0), "elements": sum(tags.values())}
return errors, warns, stats
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(2)
failed = False
for p in sys.argv[1:]:
errors, warns, stats = audit(p)
print(f"== {p}")
if stats:
print(f" {stats['bytes']} bytes, {stats['elements']} elements, {stats['paths']} paths, colors: {', '.join(stats['colors']) or 'none/currentColor'}")
for e in errors:
print(f" ERROR {e}")
for w in warns:
print(f" warn {w}")
if not errors and not warns:
print(" OK")
failed |= bool(errors)
sys.exit(1 if failed else 0)
if __name__ == "__main__":
main()
About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.