From throughline
Build an icon system in Figma — a dedicated "Icons" page populated with the user's chosen icon library (Lucide, Material, or custom SVGs) as well-named, scalable components — using the fastest, most-automated mechanism per library (for Lucide, batch-fetching the curated subset's official SVGs from the source repo and componentizing them hands-off; for Material, the official community file or importer plugin) rather than hand-generating icons or making the user copy components by hand. Use this when the user wants to set up icons, add an icon library, import Lucide or Material icons, create icon components, or build an icon set in Figma. Also trigger when the user mentions iconography, an icon page, or needs icons for their components. Make sure to use this whenever someone needs a managed set of icon components in their Figma design system.
How this skill is triggered — by the user, by Claude, or both
Slash command
/throughline:icon-system-builderThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
End state: a Figma page named **Icons** containing the relevant icon set as
End state: a Figma page named Icons containing the relevant icon set as scalable, well-named components, ready to be consumed by components (skill 4) and swapped via instance properties.
The expensive, wrong path is Claude generating icon components one by one, by hand, via the write mechanism — slow and token-hungry. Get to the end state via the cheapest mechanism that is also fully automated and produces clean, well-named components. "Cheapest" is not just Claude tokens: a mechanism that burns near-zero tokens but makes the user hunt down a community file, duplicate it, and copy components by hand isn't actually cheap — it just moves the cost onto them. The library determines the mechanism, deterministically — same library, same path every run.
Default for Lucide: fetch the curated subset straight from the official source
repo and batch-componentize it. Lucide publishes every icon as a uniform 24px
SVG at a deterministic path (github.com/lucide-icons/lucide, icons/<name>.svg),
and the filename is the canonical name (arrow-right ↔ ArrowRight in
lucide-react). So the agent can do the whole thing hands-off:
lucide-react
version, so the Figma mirror and the code package are the same generation.https://raw.githubusercontent.com/lucide-icons/lucide/<tag>/icons/<name>.svg.
A 404 means that icon doesn't exist at that version — report it and let the
user pick a replacement. (This is the name-validation gate, for free.)figma.createNodeFromSvg(svg), convert to a component, name it per the naming
contract (Step 3), and set the base size.This is fully automated, official source-of-truth, deterministic-named, and
needs no manual user steps — strictly better than a community-file copy for a
library shaped like this. This is not the old "fetch SVGs off a website and
hand-prep them" anti-pattern: that meant a human manually downloading and cleaning
files. Batch-grabbing the official repo by deterministic path and scripting
createNodeFromSvg is cheap and hands-off.
Fallbacks (only if the fetch path is blocked or the user prefers): a vetted Lucide community component file, or the official Lucide importer plugin — e.g. if GitHub is unreachable, the user is on a restricted network, or they explicitly want the community file. Name the specific resource and surface its license first (see below).
Tabler and Phosphor are first-class libraries built by the exact Lucide
mechanism — uniform per-icon SVGs published at a deterministic repo path where the
filename is the canonical name, so they get the same fully-automated,
official-source-of-truth, hands-off treatment (batch-fetch → createNodeFromSvg →
createComponentFromNode), not a community-file copy. This path is proven: a
greenfield run imported the Tabler subset by deterministic path with zero 404s and
bound the vector strokes to text/primary for theming. Prefer it whenever the user's
brand specifies Tabler or Phosphor (both are common picks).
github.com/tabler/tabler-icons, per-icon at
icons/outline/<name>.svg (and icons/filled/<name>.svg for the filled set). Pin a
release tag matching the installed @tabler/icons-react generation. A 404 is the
name-validation gate, same as Lucide.github.com/phosphor-icons/core, per-icon at
assets/<weight>/<name>.svg where weight ∈ thin|light|regular|bold|fill|duotone
(regular has no suffix; others are <name>-<weight>.svg). Pick one weight as the
base to match the code package, pin the tag, and fetch that weight's files.After fetch, bind the vector strokes/fills to text/primary (or the icon color
token) so the icons theme with the system, and name per the Step 3 contract. Fallbacks
(only if the fetch is blocked) mirror Lucide's: the library's official Figma community
file or importer plugin, license surfaced first.
Material has variant axes (outlined / rounded / sharp × fill / weight / grade / optical size), so there is no single clean per-icon file to grab — a direct repo fetch isn't the clean default it is for Lucide. Default to the official Material Symbols community file or the official Material importer plugin (Apache-2.0): name the specific vetted resource, say why, surface the license, and get the user's confirmation (see below). Only consider a direct fetch if the user pins one specific style, and even then prefer the importer. Never hand-draw or generate one-by-one.
The user's own SVGs, brought in via batched import + componentize (never hand-drawn, never one at a time). The sync layer later turns these into code via its SVGR pipeline — see Step 3.5.
Choosing a different mechanism on different runs for the same library is a bug: the library fixes the choice, so it must be deterministic.
Most projects need 40–120 icons, not 1,700+. Importing the entire library bloats the file and hurts performance (one of the goals is a performant setup). So:
Run ${CLAUDE_PLUGIN_ROOT}/references/brainstorm-before-build.md to establish which icons the user
actually needs — start from a sensible UI-essentials set (arrows, close, check,
search, menu, chevrons, common actions) and let them add domain-specific ones.
Only import the full library if the user explicitly wants it. Recorded subset can
grow later by re-running.
Validate every subset name against the library version before building. Icon
libraries add and remove icons between versions — e.g. lucide-react 1.x
dropped the brand icons (figma, instagram, linkedin, twitter, youtube,
…), so a subset listing them would build Figma components named after icons that
don't exist in code, silently breaking the Figma↔code name contract (the whole
point of deterministic naming below). Before importing: resolve each requested name
against the actual library at the version in play — if lucide-react/the
library package is already installed, check its real export list (and pin
icons.version to that); otherwise check the version's published icon manifest.
Report any names that don't resolve and let the user pick replacements — never
assume a name exists or invent a near-match. Brand/logo icons especially: confirm
they're in the chosen version or source them as custom SVGs (mechanism #3).
When a path uses an unofficial community file or importer plugin — Material's default, or a Lucide fallback — those resources are variable in quality (some popular plugins are reported buggy/slow). So: name the specific vetted resource you intend to use, briefly say why, surface its license for commercial use, and let the user confirm or pick another before proceeding. Don't silently grab whatever's first. Verify the link is still live — don't assume a stale URL/file key works; if the named resource has moved, search Figma Community for the official/most-installed equivalent and confirm before using.
Default candidates for these paths, so the choice is consistent across runs:
The Lucide official-SVG fetch is the default and needs no resource-vetting step — the source is the library's own repo, not a third-party file — but still pin the version tag and report any names that 404. If you genuinely cannot reach the Lucide repo and cannot confirm a community file or importer, say so and ask the user which resource to use — never invent a source.
icons.library.Execute the library's mechanism:
raw.githubusercontent.com/lucide-icons/lucide/<tag>/icons/<name>.svg, then in
one scripted pass build each into a named component via
figma.createNodeFromSvg. Report any 404s and let the user pick replacements
before finishing. No manual user steps. (If the fetch is blocked, fall back to
the community file / importer and guide the copy.)Execution model — sequential Figma-lane subagent with model routing. If your
host supports subagent dispatch, run the bring-in + componentize pass as a
figma-executor dispatch (balanced tier): preflight figma_get_status,
never run two Figma-touching subagents at once (the single bridge is
concurrency-1), build into a WIP: frame and finalize by build-verify-then-
replace with the read-back audit in Step 3. The subset + naming contract were
already settled with the user in Step 1, so no separate architect dispatch is
needed here. Route per ${CLAUDE_PLUGIN_ROOT}/references/agent-routing.md. If
your host has no subagent dispatch, script the SVG-to-component pass inline,
sequentially, with the user in the loop. Either way, follow
${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md.
Whatever mechanism brought them in, ensure the end state is consistent:
icon/arrow-right ↔
ArrowRight in lucide-react). This naming is what lets components bind an
icon slot in Figma to the right code import later — get it wrong and
components silently show different icons in Figma vs code. Follow the library's
canonical naming so the mapping is automatic.figma_execute, follow
${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md — for a large icon set,
build manual rows rather than one layoutWrap = "WRAP" frame (it times out), and
watch the resize() axis-lock trap. Follow
${CLAUDE_PLUGIN_ROOT}/references/figma-component-standards.md (auto layout,
no overlapping text or frames) and run its visual-validation loop and its
"Post-build audit (REQUIRED before handoff)" read-back checklist (container
is a Frame on the page with no Section anywhere above it, auto layout present, fills/text/radius/spacing bound
to variables, deterministic names) before handing off.Library icons (Lucide/Material) have their real source of truth in an npm
package — lucide-react, @mui/icons-material — not in Figma. Figma is a
visual mirror of that package. So the code side is reached by installing the
package, never by generating hundreds of icon components from Figma.
If a repo exists (workspace.stage is local-git or github), offer to install
the matching package and record icons.packageInstalled = true and
icons.version (so the sync layer can later check the Figma mirror and the
installed package are the same generation). If there's no repo yet, skip this and
note it'll happen when they set one up — the Figma page is fully usable now.
Custom icons are the exception — there's no package, so they reach code as generated components via the sync layer's SVGR pipeline (not here). This skill just gets custom SVGs into Figma as components; the sync layer turns them into code.
Icons are the main thing components swap into slots. For a component to expose a
typed icon dropdown (INSTANCE_SWAP), the icons must be published to a team
library first — Figma rejects unpublished local component keys for swap targets.
This is the natural moment to publish, before components are built.
Read ${CLAUDE_PLUGIN_ROOT}/references/figma-publishing.md and follow it:
figma.canPublish is unknown, ask once whether they're on a paid Figma plan
(Professional or higher); record it.figma.libraryPublished = true and figma.publishedAt. Mention that adding
components later means a quick re-publish.Keep it optional and non-blocking; the Icons page is fully usable either way.
Show the user the Icons page. Iterate if needed. Update the manifest:
icons.built = true, icons.library, icons.version (for library icons),
icons.subset (the imported set), and icons.packageInstalled if the code
package was installed. Append icon-system-builder to completedSkills. Note
they can re-run to add more icons. Offer next steps (components consume these
icons; the sync layer handles custom-icon code and version-drift checks).
createNodeFromSvg over a batch of official Lucide SVGs is fine and is the
default — that's automated import, not hand-drawing.)npx claudepluginhub jrpease/throughline --plugin throughlineGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.