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.

  • Design & UX
  • $svg-logo-design
  • 203 lines
  • 3 files
  • ~2,100 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- svg-logo-design

Installs to ~/.agents/skills/svg-logo-design/ for every project on this machine.

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.

~/.agents/skills/svg-logo-design/SKILL.md

--- name: svg-logo-design description: Designs logos, symbols and wordmarks as hand-built SVG - concept from the brief, construction on a grid, monochrome first, optical corrections,… license: MIT ---

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:

TypeUse whenRisk
Wordmark (lettering only)Name is short, distinctive, and must be learnedWeak as an app icon; needs a monogram fallback
Monogram / lettermarkLong name, needs a compact iconGeneric unless the letterform is modified
Abstract symbolProduct spans many offerings; long-term brandMeans nothing until the brand earns it
Pictorial symbolA concrete idea is central (a leaf for a garden app)Clichés: check it is not the first stock icon result
Combination markMost startups: symbol for icons, wordmark for headersTwo 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.svg for 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

ProblemCorrection
Circle looks smaller than square of same heightOvershoot round shapes 2-3% beyond the baseline and cap line
Horizontal strokes look heavier than verticalMake horizontals 5-10% thinner
Mark looks left-heavy or low when centredShift the visual centre up about 2-4% of height
Counters close up at 16pxOpen counters and gaps to at least 1/16 of the viewBox
Reversed (white on dark) looks bolderProvide 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:

FileContent
logo-symbol.svgSymbol only, square viewBox
logo-horizontal.svgSymbol left, wordmark right, gap = 0.5x symbol width
logo-stacked.svgSymbol above wordmark
logo-wordmark.svgWordmark only
*-mono-black.svg, *-mono-white.svgOne-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 removeViewBox enabled (default in older SVGO versions) breaks scaling; confirm viewBox survives 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.yamlgenerated from this page

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.py114 lines, 4.6 KB
#!/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.

esc