svg-infographic
SkillWeb & browsingAuthor technical/structured SVG infographics and diagrams, then render them to crisp PNG with a headless browser. Best for architecture diagrams, topology maps, flows, before/after comparisons, nested/onion layer models, roadmaps, decision matrices, and social-ready technical one-pagers. Prefers clean line icons in soft tinted circles. First-class Korean/CJK text. Includes an opt-in "tidy hand-drawn" sketch preset (paper background, Korean handwriting font, rough strokes, highlighter). Not for photo-heavy or illustration-heavy graphics, statistical charts, or mascot/character illustration.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the svg-infographic skill
What this skill tells your AI
The instructions your AI receives, as published by kyungseo/skillstead in skills/svg-infographic/SKILL.md and read by ahel’s review.
Use this skill when the user wants a technical or structured infographic/diagram from a description, or asks to export an SVG to PNG.
Good for: architecture diagrams, cloud/network topology, component/layer diagrams, before/after, process/data flow, nested "onion" models, roadmap/risk maps, decision matrices, social-ready technical one-pagers.
Do not use for: photo/illustration-heavy marketing graphics; data-accurate statistical charts (bar/line/scatter/heatmap — use a chart tool); mascots or character illustration; custom logo design or bespoke icon design.
Nuances: a simple qualitative 2×2/3×3 matrix or a status-count badge is fine (it's structure, not a data-accurate chart). Using the built-in line-icon set is expected and encouraged — the non-goal is designing new brand marks, not using icons. A hand-drawn / sketchnote feel is supported as the opt-in sketch preset (references/sketch.md) — "tidy hand-drawn": hand-drawn surface, computed layout; mascots and scene illustration stay out of scope.
Package map — this skill ships as a directory; load the parts on demand:
| File | When to read |
|---|---|
references/design-kernel.md | Before choosing colors, tokens or typography — the canonical skin contract: 11-role token model, domain aliases, versioned profiles + skin.mjs resolver, typography profile SSoT(references/typography/), approved contact sheet |
references/types/selection.md | Before choosing a type — the generated routing view: content signal → TypePack → its spec (derived from the TypePack manifest; migrated types only) |
references/archetypes.md | For the cross-type premium base recipe (the default visual language). Every per-type rule set has moved to its TypePack spec; the per-archetype sections here are pointer tombstones only |
references/authoring.md | Always, before writing SVG — detailed geometry/connector/panel/emphasis/color rules and the full icon set; also the manual render fallback |
references/sketch.md | Only when the user asks for a hand-drawn / sketchnote / handwritten feel — the opt-in sketch preset (paper, handwriting font, rough filters, highlighter) |
scripts/render.mjs | Canonical renderer (Node 18+, stdlib only) — lint gate → browser discovery → 2× render → exact IHDR verification in one entrypoint; works from any shell incl. Windows CMD/PowerShell without Git Bash |
references/presentation/contract.md | Only when the user explicitly asks to place an already verified canonical result on a notebook, wall, board, portrait monitor, ambient screen, or a custom registered surface |
scripts/projection.mjs | Explicit opt-in derived-sibling builder/verifier; never replaces or post-processes the canonical SVG/PNG |
scripts/render.sh | Thin POSIX/Git-Bash wrapper that delegates to render.mjs (adds only the no-Node diagnostics) |
scripts/check-svg.mjs | Source lint gate (Node 18+, standard library only; no npm install) — the renderer runs it automatically; run it directly while iterating on the SVG source |
scripts/check-layout.mjs— layout contract guard (container padding/symmetry, equal-gap distribution, atomic card clusters; design-kernel §7). The renderer runs it automatically on layout-annotated SVGs.scripts/compose.mjs— composition tooling: validate a Composition Plan, place TypePack fragments into template slots (translation-only), and verify the composite against its receipt (design-kernel §8).scripts/skin.mjs typography-check— effective-font cascade guard (missing wrapper, inherited weight, unannotated secondary, remote font; it inspects the final file itself — standalone and composite alike). The renderer runs it automatically on sketch/typography-annotated SVGs.scripts/font-probe.mjs— browser runtime font receipt (FontFaceSet load + computed family/weight of scoped KO/EN samples; evidence level: computed+load, NOT rendered-face proof). |scripts/skin.mjs| Skin profile resolver —validate/resolve/registryoverreferences/skins/(palette SSoT); use resolved tokens instead of inventing hex values |
0. Preflight — confirm, then offer to change
Before drawing, confirm visual intent, audience, output ratio, and language. Then state the defaults (§6) and note the user can change any of them. Propose an output directory inside the current project and confirm before writing files.
Before promising automated lint or using the bundled render.sh, check whether node --version reports Node.js
18 or newer.
Node is not required to install or discover this skill, or to author an editable SVG. It is required for the
bundled source lint and therefore for a machine-linted handoff.
If Node 18+ is missing:
- Detect the operating system and an available trusted package manager. Confirm that its candidate package provides Node 18+ before showing the exact install command and asking in the conversation language: "Would you like me to install Node.js 18+ and continue with automated validation?"
- Install only after the user explicitly approves that system change. Use the detected package manager; do not
substitute a remote
curl | shinstaller. If the command needs elevated privileges, surface that approval rather than hiding it. - Verify
node --versionafter installation, then resume the source lint and render workflow. If installation fails or still provides an older version, report that result and do not retry through a different source without another approval. - If the user declines, or no safe package-manager route is available, keep the pre-v0.7.1 quality floor:
complete every item in the manual source checklist, do not invoke
render.sh, and use the Node-free Chromium path inreferences/authoring.md§8 to render a 2× PNG. Verify its dimensions and complete the §7 visual QA. State: "Automated source lint was not run because Node.js 18+ was unavailable. The manual source checklist and PNG render/visual QA passed." Never label this fallback as machine-linted. - Only if a Chromium-based browser is also unavailable, deliver an SVG-only draft and state that neither automated source lint nor PNG visual verification ran.
Classify the input mode first (it sets how much to ask before drawing):
- brief-first — only a topic/goal is given → ask up to ~3 focused questions (audience, key message, ratio), then propose the structure.
- source-first — a doc, notes, or paste is provided → summarize the source and agree the key message before drawing; don't transcribe it box-for-box.
- research-first — "just draft it" → state your assumptions (and any missing data) up front; external lookup may be unavailable, so proceed from the given material plus explicit assumptions.
Lead with the conclusion. Prefer a conclusion-style title over a topic label — "Phased Migration Cuts Cutover Risk" beats "Migration Plan". Sketch the one-to-two-sentence story spine (context → tension → resolution) before placing boxes. Exception: for a named pattern or archetype showcase, a concise topic/pattern title is fine as long as the subtitle or <desc> carries the conclusion.
1. Pick an archetype (shape first)
Pick from the content signal. Read references/types/selection.md first: if the signal routes to a TypePack, that spec is the only rule set — it owns the input contract, the fit/variant contract, the layout formulas and the checks, and references/archetypes.md holds nothing but a pointer for it. If the signal does not appear there, the type has not been migrated yet: read its section in references/archetypes.md, which stays normative for exactly those types.
| Content signal | Archetype |
|---|---|
| systems/components and their links | Topology / component |
| ordered steps or handoffs | Flow (swimlane variant for parallel rails) |
| approval / gate on a simple request path | Approval / sequence-lite |
| options, trade-offs, qualitative scoring | Decision / risk matrix, or Cards |
| old vs new | Before / after |
| layered capability or containment | Layer stack, or Nested / onion |
| time, phases, milestones | Roadmap / timeline |
| a few headline items or numbers | Cards / KPI stat grid (not a chart) |
2. Layout pass — numbers before SVG (required)
This step is the main defense against render-fix loops. Do the arithmetic first; never place a box at an eyeballed coordinate. Produce a short numeric plan (a scratch table of coordinates is enough), then author the SVG from those numbers.
- Canvas. Pick a preset: compact doc 680w · wide architecture 1400×900 · 16:9 slide 1600×900 · social portrait 1080×1350 (4:5). Height is flexible for the doc width. Fix outer margins (≥ 40px wide canvases, ≥ 24px at 680w).
- Regions (PageFrame). Derive the page from the PageFrame contract (
references/design-kernel.md§6): safe area → header region → breathing gap → content region (the TypePack contentBox) → optional support region → optional footer, each with its own gap token; absent elements collapse with their gaps. The page header is the H-C editorial stack: optional muted eyebrow row → H1 (1–2 lines) with the computed--focustitle-keyline at its left (canonical default — derived from the H1 line-box, design-kernel §6; the eyebrow square locator is the explicit alternative variant, never doubled with the keyline) → optional muted subtitle → generous breathing room. No vertical accent rail (rejected composition — do not regenerate it). For every panel header, computeheaderH = top padding + title line box + title/subtitle gap + subtitle line box + bottom padding; place the divider only after that budget. Do not squeeze the title area until the copy happens to fit. - Grid arithmetic. For each row of n cards inside a region: choose
cardWandgap(gutter 24–32px), then verify the last edge before drawing:start + (n−1)·(cardW+gap) + cardW ≤ region_right − padding. Same check vertically for columns/stacks. If it doesn't fit: shrinkcardW/gap, wrap to a second row, or widen the canvas — decide now, not after a render. - Text budget. For each box, set lines × chars/line from the box width: ~28–36 Latin chars per line at body size, Korean ≈ 60% of that; 2–3 lines max per box. Edit the copy to fit the budget before writing SVG — abbreviate long tokens now. SVG has no auto-wrap; every line you plan here becomes one
<tspan>. - Type scale — unified across the diagram (never vary per box):
- 1080-wide social: H1 46 / section 24 / card title 25 / body 19 / caption 16
- 1400–1600 wide: H1 40–44 / section 22 / card title 20–22 / body 16–17 / caption 13–14
- 680-wide docs: title 22 / box label 14 / caption 11
- Icons and copy clusters. Derive every icon-circle center from card geometry (e.g.
cy = card_y + card_h/2). Compute the text cluster's visual bounds from its planned line boxes and center that cluster on the samecard_y + card_h/2; never hand-tune either side per language. EN and KO variants must share the same formulas. - Connector corridors. Budget connectors like cards:
corridor = target_visual_left − source_visual_right, and subtract the marker's real footprint (markerUnits="strokeWidth"multiplies it by the stroke width — formulas inauthoring.md§3). If no readable shaft survives, choose a compact arrow, a transition glyph, or a reflow now, not after a render.
3. Author the SVG — core rules
Read references/authoring.md for the detailed rules and the reusable icon set. The render-critical core:
- Root:
<svg xmlns viewBox="0 0 W H" width=W height=H role="img" style="font-family:...">with<title>/<desc>— font identity (face, weight, asset, digest, license) is owned by the typography SSoT (references/typography/typography-v1.yaml), and how the font travels by the font-delivery policy (references/delivery/font-delivery-v1.yaml). The two axes stay separate, so adding fonts or output modes does not multiply the combinations.- portable (default, acceptance-grade): from flat's bundled Pretendard 400/700 faces, subset only the glyphs that artifact actually used and embed them as an
@font-facedata URI. The alias is the neutral name the policy fixes — Pretendard is a Reserved Font Name, so a subset (a Modified Version) may not carry it. The artifact renders with the same geometry even where the target environment lacks the font. Gallery and release evidence are produced in this mode only. - system (lightweight, environment-dependent): no embedding; relies on the installed font stack. Light and editable, but a diverging fallback changes wrapping and geometry, so it is never used as acceptance evidence.
- What an editable artifact requires: an embedded subset serves viewing and render fidelity, not editability (edit the copy and any glyph outside the subset becomes tofu). To open and edit the SVG, or to edit it after importing into PowerPoint, Pretendard must be installed in the target environment — PowerPoint does not preserve fonts embedded in an SVG.
- The subset tool is a pinned build-only dependency:
pyftsubsetfromfonttools 4.53.1(plusbrotli), with fixed options. When the tool is absent it fails rather than sliding quietly into a full embed or a system fallback. Viewing or verifying an existing artifact (lint, layout, render, audit) does not need it. - The sketch treatment embeds a Hi Melody glyph subset the same way (neutral alias, weight fixed at 400).
- Verification:
skin.mjs typography-check(the static cascade — the renderer runs it automatically as a hard gate when it detects sketch/typography annotation),skin.mjs delivery(policy, alias and RFN cross-check), andscripts/font-probe.mjswhen needed (a runtime load/computed receipt — not proof of the rendered face). If flat KO shows tofu (□) on Linux in system mode, install Pretendard orfonts-noto-cjk.
- portable (default, acceptance-grade): from flat's bundled Pretendard 400/700 faces, subset only the glyphs that artifact actually used and embed them as an
- Direct paint + role annotations — colors encode role, not decoration, but the canonical SVG is authored in the PPT-oriented portable form from the start (
references/design-kernel.md§5): every paint-bearing shape carries directfill/strokevalues plusdata-fill-role/data-stroke-roleannotations. Recolor by re-running theskin.mjsmaterializer against a profile, never by hand-editing hex. Novar(--…),currentColoror paint classes in canonical output:
<!-- Paint values come from the resolver — get them with
`node scripts/skin.mjs resolve current --mode light`. Never invent hex values:
the palette SSoT is references/skins/ + registry (design-kernel.md §3).
Roles: canvas/surface/surface-tint/ink/muted/rule/focus/positive/warning/
danger/on-focus; aliases: edge/api/compute/data/external/icon. -->
<rect data-fill-role="canvas" fill="#F7F7F5" width="720" height="1020"/>
<rect data-fill-role="surface" data-stroke-role="edge-line"
fill="#FFFFFF" stroke="#2E6DA4" rx="10"/>
<text data-fill-role="edge-ink" fill="#193C5A">…</text>
<path data-stroke-role="edge-line" stroke="#2E6DA4" fill="none" …/>
- Boxes: rounded rect
rx="8"(wide bandsrx="12–22"), hairline borderstroke-width:1. Each box = tinted fill + same-family border + same-family text (one semantic color family per box). - Vertical centering: center text with
dominant-baseline="central"andyat the box's vertical center. Two lines straddle the center: title atcenter−11, sub atcenter+10, bothcentral. Never rely on the default alphabetic baseline for box labels — it sits high. - Composite scenes (intent-first selection): one request may need MORE than one type — do not invent a new single type for it. Example prompt: "Use svg-infographic to summarise the key points as four cards along the top and show how the items relate as a tree below. Make it 4:5 for a social post." → plan this as a summary cards (primary) + tree (supporting) composite scene: the composition layer owns the slot split, gaps, hierarchy and reading order; each TypePack owns only its slot interior; both share one skin/typography/icon/connector contract (design-kernel §8). timeline+comparison, process+callout, architecture+decision matrix follow the same rule. Relationships default to numbered/labelled semantic bindings — draw connector lines only when the line itself carries meaning. Bounded: one primary + one or two supporting modules; if space runs out, reduce a supporting variant or split pages — never shrink text/arrows.
- Layout contracts for repeated structures: annotate panel headers and icon-text cards with the opt-in
data-layout-rolecontract fromauthoring.md§4/§7; a card contract names its actual background rectcard-frame, so frame, icon, and complete text cluster derive from one center. The legacypage-title-headerrail contract applies only to pre-kernel examples — new headers follow the H-C cluster (design-kernel §6); the cluster lint contract replaces the rail contract in CP3. Keep unsupported transforms or unusual typography outside annotations and verify them manually. - Wrapping: one planned line = one
<tspan x=.. dy=..>; keep to the §2 text budget. Inside an opt-in layout contract, use plain numericy/dyand non-nested<tspan>lines so the lint can accumulate the complete vertical line box. - Arrows: define one
<marker>arrowhead, usemarker-end. Solid = sync/request, dashed (stroke-dasharray="5 4") = async/batch/private. The default head is an open-V stroked marker sized by visible geometry:visible ≈ markerWidth × 8/12, aim visible ≈3× the shaft →markerWidth ≈ 4.5 × shaft(sizing table inauthoring.md§3) — filled triangles only as a deliberate choice with the same visible-extent arithmetic.markerUnits="userSpaceOnUse"is mandatory on every referenced marker and the lint gate enforces it — the defaultmarkerUnits="strokeWidth"multiplies the head by the stroke width. SetrefXso the tip lands on the path endpoint; leave an 8–12px gap between tip and target box and keep a visible shaft behind the head; pick each connector's form (standard / compact / curved / transition glyph / reflow) from the corridor budget, and prefer the gentle single-bend curve recipe when boxes sit at different heights (authoring.md§3). - On-focus text is light: any label on a saturated fill carries
data-fill-role="on-focus"with a direct light fill — never dark text on a mid/dark saturated fill, and never rely on a blankettext{fill}rule to sort it out. - Emphasis toolkit: stroke + soft shadow + a number/status badge + a corner label + a filled icon badge. No top accent bar on cards (corner-smear and badge-collision failure modes — details and narrow exception in
authoring.md). - Icon-first (default on): a line icon in a soft tinted circle (
r≈34–38,data-fill-role="icon-tint"+ direct fill) per card/node, authored via the icon library (<use>as convenience), expanded to concrete role-annotated paths with direct paint in canonical output (authoring.md§7). Number badge only when sequence or cross-reference matters — never icon + redundant number.
4. Pre-render checklist (source-level — run before every render)
Run the lint gate first for a verified handoff — it machine-checks the deterministic subset of this list (ids/references, root viewBox, marker units and footprint, opt-in header/card layout geometry, high-confidence Latin/CJK text overflow) with file/line, measured values, and a suggested fix per finding. If Node 18+ is unavailable, follow the §0 approval/fallback branch; the manual fallback does not count as a machine-linted handoff.
node scripts/check-svg.mjs diagram.svg # render.sh runs this again as a hard gate
Hard errors must be fixed before rendering (render.sh refuses at exit 5). Warnings are low-confidence estimates — disposition each one deliberately (fix it, or verify it in the §7 PNG pass); never treat a warning as a pass. Then walk the rest of the checklist — layout arithmetic and design judgement stay yours:
- Containment re-check: the §2 last-edge/bottom-edge arithmetic still holds for what you actually wrote (cards, arrows, badges, labels — including any element you added while authoring). Judge visual bounds, not just the fill rect — half the stroke width, shadow spread, and children drawn outside the base rect count; in a padded panel, an edge that merely touches the parent is a fail, not a pass (formula in
authoring.md§1). - Text budget: no
<text>/<tspan>line exceeds its planned chars/line; box labels usedominant-baseline="central"with computedy. Pill/badge fit: every pill/badge background covers its actual label width plus ≥ 14–16px padding per side, and EN/KO shared geometry fits the wider language's label (formula inauthoring.md§2). - References resolve: every
<use href="#id">matches a defined<symbol id>; everymarker-endreferences a defined<marker>; no danglingurl(#…). - Contrast roles: every label on a saturated fill carries
data-fill-role="on-focus"with a direct light fill; no blankettext{fill}rule that overrides on-focus labels via inheritance. - Corner clearance: badges / status labels / corner icons in the same card corner region have ≥ 20–24px between bounding boxes.
- EN/KO parity: if generating both, the two variants share identical geometry formulas — only text (and text budget) differs.
- Root sanity:
viewBoxmatches the intended W×H;<title>/<desc>present; font stack on the root. - Connector budget: every connector's corridor and marker footprint are computed (§2.7); each standard arrow keeps a visible shaft (≥ 12–16px, not hidden under a card/panel by paint order) plus the 8–12px tip gap — a head-only arrow is a fail even when the gap is right; tight corridors carry a deliberate form choice (compact arrow, transition glyph, or reflow —
authoring.md§3); EN/KO share connector geometry and semantics.
5. Render to PNG (2×)
The canonical renderer is render.mjs (Node 18+ stdlib): it runs the §4 source lint as a hard gate (exit 5 =
lint errors), discovers a Chromium-based browser (PATH plus the documented known paths), discloses the exact
executable/version, renders at 2×, and verifies the PNG IHDR dimensions exactly. It needs no shell beyond Node —
on Windows run it directly from CMD or PowerShell, verbatim, with no PowerShell scripting and no Git Bash. Use the
path of the skills root this package is actually installed under.
Claude Code install (.claude\skills):
node .claude\skills\svg-infographic\scripts\render.mjs out\diagram.svg
Codex install (.agents\skills):
node .agents\skills\svg-infographic\scripts\render.mjs out\diagram.svg
On POSIX/Git-Bash environments the familiar wrapper delegates to the same core (exit 6 = Node 18+ missing — return to the §0 approval/fallback branch rather than bypassing the gate):
bash scripts/render.sh diagram.svg # → diagram.png (2×)
bash scripts/render.sh diagram.svg out.png --transparent
SVG_INFOGRAPHIC_BROWSER=<executable> overrides discovery for owner-approved retries — it must still point at a
Chromium-based browser.
Keep wrapper/intermediate files in the session scratchpad, not the repo. In native PowerShell/CMD the canonical path is still node scripts/render.mjs (above) — the manual fallback in references/authoring.md §8 exists only for the §0 no-Node branch. If no Chromium-based browser is available at all, deliver the SVG only and state the limitation.
The documented Chromium path is the canonical renderer. Non-negotiables:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 78
- Forks
- 15
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
svg-infographic- Source
- github.com/kyungseo/skillstead