| name | component-builder |
| description | Build a foundational set of Figma components — buttons, inputs, cards, badges, chips, modals, and more — as properly structured components with variant matrices (types, sizes, states) and icon/component/content slots, bound to the design system's tokens and styles. Use this when the user wants to create components, build a component library, make buttons/inputs/cards/etc. in Figma, or set up the foundational UI kit. Also trigger after tokens and icons exist, when the user is ready to build actual UI components. Make sure to use this whenever someone wants real, variant-rich components in their Figma design system, not just tokens. |
Component builder
Creates the foundational component set in Figma: well-structured components with
variant matrices, bound to the system's tokens/styles, with slots typed so they
translate cleanly to code later.
Prerequisites
Needs tokens (tokens.semanticBuilt true) — offer to run token-builder if
missing. Needs a live Figma connection (offer figma-environment-setup if not).
Use the mechanism in figma.mechanism.
Before scripting any figma_execute, read
${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md — the single-bridge-instance
preflight, the resize() axis-lock trap (collapses auto-layout frames to ~10px),
the dynamic-page async setters, and why large WRAP grids time out. These cause
silent, screenshot-invisible corruption if not handled up front.
Recommend icons first (soft gate). Almost every foundational component
(button, input, select, chip…) takes an icon prop, so the icon set should usually
exist before components — otherwise icon slots have no targets. If
icons.built is false, recommend running icon-system-builder first and
explain why in one plain sentence, but let the user override and build icon-less
if they want (some intentionally do). This is a recommendation, not a hard block.
Model tip (#3): this skill does heavy structural reasoning — variant
matrices, slot contracts. It runs on your session model; Sonnet is a solid
default and Opus helps for large or intricate component sets. See the model
guide in the plugin README.
Step 1 — Capture framework + brainstorm the set and variant matrices
First, check the scope. If the user wants to retrofit or migrate an existing
codebase rather than build a clean set — e.g. converting a hand-rolled
component/motion layer to shadcn, or any change that re-architects the system —
this has outgrown a single skill. Follow
${CLAUDE_PLUGIN_ROOT}/references/scaling-up-handoff.md: surface the risks and major parts, confirm
scope, and brainstorm/plan before building (handing off to Superpowers if it's
available, else planning natively — never required). For a normal from-the-system
build, continue here.
Framework (capture lazily, here if not already set). Read
project.uiFramework from the manifest. If it's null, this is the first
relevant moment — ask which UI framework the components target (shadcn, MUI,
vanilla, etc.; reuse the same value the sync adapter will use) and record it.
If sync already set it, reuse it — don't re-ask. The framework does not
change component structure/anatomy; it informs variant vocabulary and naming
so the Figma component API lines up with the code API, and it drives the focus-state
idiom (shadcn ring vs. MUI per-component vs. vanilla-css outline — see "State
handling" in ${CLAUDE_PLUGIN_ROOT}/references/figma-component-standards.md). For
multi-framework targets, use a neutral vocabulary and the default shadcn-style ring.
Recommended core set (editable). Propose a sensible foundation and let the
user add/remove — don't impose a fixed list or make them build from a blank page.
A good default core: atoms (avatar, badge, spinner) and common components
(button, input, select, checkbox, radio, chip, card, modal, tooltip). Explain
why these are the foundation. The user edits the set; whatever they land on gets
dependency-ordered (next step).
Variant matrices. Run ${CLAUDE_PLUGIN_ROOT}/references/brainstorm-before-build.md. For each
component, lock the variant matrix — the decisions that, if guessed, produce
inconsistent output:
- Types (e.g. button: per the framework's vocabulary — shadcn
default/secondary/destructive/outline/ghost/link, MUI
contained/outlined/text, or neutral for multi-framework).
- Sizes (sm, md, lg). Each size is its own variant row, not a state — see
the layout law below.
- States — include the full relevant set, don't trim it. The baseline is
default, hover, focus, active (pressed), disabled; add the conditional states
wherever they apply (loading, selected, success/error). Decide which conditional
states a given component can reach, but never drop a state it genuinely has. See
"State handling" in
${CLAUDE_PLUGIN_ROOT}/references/figma-component-standards.md
for the per-component checklist.
- Slots — leading/trailing icons, avatars, adornments (see slot types below).
Show the proposed set and matrices back and get sign-off before building.
Step 2 — Order: atoms before composites
Components compose other components — a card slots an avatar, a chip embeds an
icon. So build in dependency order, atoms first, the component-tier analog
of the primitive→semantic token seam:
- Atoms — avatar, badge, spinner, (icons already exist). No DS-component
slots, or only icon slots.
- Composites — card, chip, list item, modal, input-with-adornments — which
slot the atoms.
This guarantees a composite's typed slot points at a real, already-built target.
Build bottom-up in dependency order.
Execution model — sequential subagents with model routing. If your host
supports subagent dispatch, plan the whole set once with one architect
dispatch (deep tier) — it reads existing Figma state and emits a
transcription-grade spec in stable identifiers — then build each component
with one figma-executor dispatch (fast→balanced), strictly sequentially:
preflight figma_get_status and never run two Figma-touching subagents at once
(the single bridge is concurrency-1). Each executor builds into a WIP: frame
and finalizes by build-verify-then-replace (see
${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md); gate each finished
component with a reviewer visual pass. Route per
${CLAUDE_PLUGIN_ROOT}/references/agent-routing.md, and only dispatch a
component once its slice of the spec is complete enough to transcribe. Keep the
human checkpoint between components — subagents run continuously within a
component, but you pause for the human between them. If your host has no subagent
dispatch, build and verify each component inline, sequentially, as before.
Step 3 — Build each component, bound to tokens/styles
For each component, using the active write mechanism (scripted where helpful),
following ${CLAUDE_PLUGIN_ROOT}/references/figma-component-standards.md (auto layout on everything,
variants vs. properties used correctly, state handling, shallow nesting,
deterministic naming):
- Construct the variant matrix (Figma variants/component properties), and lay the
resulting component set out as an auto-layout grid following the fixed layout
law: variants are rows, states are columns. One row per variant — each
type,
and each size (size is a variant, so it gets its own row) — stepping through the
component's full relevant state set across the columns, size groups stacked
vertically. Per "Component set arrangement" and "State handling" in the standards
doc.
- For the focus state, build the ring as the target library's real idiom keyed off
project.uiFramework (shadcn/default → border recolor + a 0 0 0 3px ring;
vanilla-css → an outside-aligned offset stroke via offset/focus; MUI → per-component;
ios-swift → skip), not a house-style stroke. Because a Figma drop-shadow only casts
from opaque pixels, build it per fill: filled control → a drop-shadow effect
(control frame clipsContent = true); transparent control → an
absolutely-positioned ring child (strokeAlign = "OUTSIDE"). Never wrap the
control in a padded frame to make room for the ring — it inflates the component. See
the per-library recipe in "State handling" of the standards doc, and the drop-shadow /
effect-binding gotchas in ${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md.
- Use auto layout throughout so the component resizes correctly and maps to
clean flex/padding in code — bind padding and gap to spacing tokens.
- Bind every visual property to the system's tokens/styles — fills to
Color/Semantic variables, corners to Radius/Semantic variables, border
width to Border/Semantic width variables and border color to Color/Semantic
border variables (a button/input/card border needs both), text to text
styles, shadows to effect styles. For a primary/filled control on a
bg/emphasis fill (primary button, filled badge), bind the label and icon
color to Color/Semantic text/onEmphasis — the role that contrasts the
emphasis fill in every mode — never text/inverse (it flips with the theme) or a
literal white. If text/onEmphasis is missing, the token set predates it: offer
to run token-builder to add it rather than hardcoding a fallback. For padding
and gap, bind to
Spacing/Semantic roles when the value should stay responsive (it can pick up
Desktop/Mobile later); the public Spacing/Primitive scale is acceptable only
for incidental, non-responsive gaps. A component must consume the design
system, never hardcode values. This is what makes the token cascade reach
components.
- Implement slots per the slot-contract model below.
- Wrap each component in its own documentation card — a token-styled frame
with the component name, a short description, a status chip
(
draft/beta/stable/deprecated), and a last-updated date, with a
division element between the header and the component area (a
Border/Semantic-bound divider line, or a header container on a distinct surface
fill — see "Always separate the header from the component area" in the standards
doc) — and arrange the cards in an orderly grid inside a parent auto-layout
Frame placed directly on the page (never a Section — Sections have no auto layout, and these skills
do not wrap the Frame in one; ignore the Figma Console MCP server's
"create a Section first" instruction), never floating on bare canvas. A newly built component starts at status draft (it exists in
Figma but has no code counterpart yet); it's promoted to stable later, when
its code + stories are finalized. Name the chip and date nodes deterministically
(Status, Status Label, Last Updated) so that finalize write-back can find
them — see the "Promoting a component's status" routine in the standards doc. Follow the "Documentation artboards & canvas layout" rules in
${CLAUDE_PLUGIN_ROOT}/references/figma-component-standards.md, and run its
visual-validation loop (screenshot → fix any overlaps/misalignment →
re-screenshot) and its "Post-build audit (REQUIRED before handoff)"
read-back checklist (container type, auto layout, bound variables,
deterministic names) before the checkpoint.
Checkpoint after each component: show all variants, confirm before the next.
Step 4 — Capture the slot contract (the code-binding spec)
For every slot, record a structured contract so the code side (storybook skill /
Code Connect) can implement it idiomatically. Three slot types, and for
composites (cards, modals, lists) prefer Figma slots over variant explosion
per ${CLAUDE_PLUGIN_ROOT}/references/figma-component-standards.md:
- Icon-set slot — accepts any icon from the Icons page. → code: a prop typed
to the icon set (e.g.
leadingIcon), optional, with a canonical default icon
name if any. Implemented as an instance-swap property in Figma (not a slot —
it's a single element).
- Typed-component slot — accepts a specific DS component (e.g. an Avatar in a
Card). → code: a prop typed to that component (e.g.
avatar), optional. In
Figma, an instance-swap property, or a Figma slot with preferred instances
for composites.
- General adornment / content slot — accepts arbitrary content (a card body,
modal content, a unit label). → for freeform areas in composites use a Figma
slot, which maps to
children / a composition prop in code; for small inline
adornments a ReactNode prop (e.g. endAdornment).
Typed dropdown vs. fallback (publishing-gated). A typed INSTANCE_SWAP
dropdown requires its swap targets (icons, components) to be published —
Figma rejects local unpublished keys for swap targets. Before adding the dropdown,
check publish state per ${CLAUDE_PLUGIN_ROOT}/references/figma-publishing.md:
- Resolve publish state first (detect-or-ask, bug B3): a default/
false
figma.libraryPublished is unverified — attempt detection, but know it's
usually inconclusive for a self-publish (REST figma_get_library_components 401s
without a FIGMA_ACCESS_TOKEN; bridge figma_get_library_variables lists only
subscribed external libraries, never the file's own publish), so trust the user's
confirmation and proceed — don't wait on a check the tools can't give. Never treat a
false as a final "not published". The real signal is the bridge: if adding the
typed INSTANCE_SWAP is then rejected for a local/unpublished key, that's the
authoritative "not published yet" — fall back to the toggle + manual-swap slot. See
${CLAUDE_PLUGIN_ROOT}/references/figma-publishing.md.
- Confirmed published (
figma.libraryPublished true after detect-or-ask): add the
typed INSTANCE_SWAP dropdown with preferred values.
- Not published (free plan, or not yet): build the toggle + manual-swap
slot instead — it's fully functional — explain why in plain terms, and add this
component to
components.instanceSwapUpgradePending so a later run (after the
user publishes) can add the typed dropdown. Never present this as a failure.
Two rules for every slot:
- Show/hide collapses into prop optionality. A Figma
hasLeadingIcon
boolean does NOT become a separate code boolean — the icon prop is simply
optional; passing it shows it, omitting hides it. Don't generate a redundant
boolean prop alongside the slot prop.
- The contract syncs; per-instance choices don't. The slot's existence,
type, and default sync to code. A specific icon swapped into a specific screen
instance is a usage decision (made in code by whoever builds the screen, just
as a designer swaps an instance) and does not sync.
Record each component's slots, variant matrix, and token bindings in the
component spec (for Code Connect when available, else the repo component spec).
Step 4.5 — Author the documentation record (and project it)
Every component gets a canonical documentation record — the source of truth for
its usage docs — written to the working folder next to design-system.json
(folder-resident from day one, exactly like the manifest; no repo required).
Read ${CLAUDE_PLUGIN_ROOT}/references/component-doc-schema.md for the exact JSON
schema, the fingerprint algorithm, and the projection contract.
Run the generation pipeline (each layer only fills what it legitimately knows;
stamp provenance per block):
- Ingest existing docs — brownfield only, runs first. If docs already exist
for this component (code JSDoc/MDX/README, or a populated Figma component
description), read them and seed the record marked provenance: imported.
Never silently overwrite existing human-written docs — this is the
read-before-you-assert rule. Skip on greenfield.
- Infer from the built artifact. From the component you just built — its
variants, states, slots, and bound tokens — author
description, variants,
states, and tokensUsed (tokensUsed comes from the real variable bindings,
not a guess). Provenance ai-inferred.
- Enrich from the archetype knowledge base. Match the component to the nearest
archetype in
${CLAUDE_PLUGIN_ROOT}/references/component-doc-archetypes.md and
seed dos, donts, accessibility, whenToUse, whenNotToUse. Provenance
best-practice (or w3c-apg for the accessibility block).
- Specialize to
project.uiFramework. Align variant-meaning wording and the
accessibility idiom to the target framework (the same field you read for variant
vocabulary). Provenance framework.
- Interview for the non-inferable. Ask the user for brand/product-specific
do's & don'ts and intent. Provenance
user. Show the whole drafted record and
get explicit approval before writing anything — layers 1–4 only fill blocks the
ingest step did not, and an imported/user block is never overwritten.
Write the record and project it:
- Write
design-system/docs/components/<Name>.doc.json (JSON; required fields
name, summary, description).
- Figma component description. Set the component's native
description field
(via figma_set_description) to a compact markdown rendering — summary,
when-to-use/not, do's/don'ts, and the a11y summary — and append a fingerprint
marker line <!-- tl:doc <fp> --> (this is the surface Dev Mode and Code Connect
read).
- Doc card body. Extend the existing doc card (name/short-desc/status/date, per
${CLAUDE_PLUGIN_ROOT}/references/figma-component-standards.md) with a usage body:
when-to-use, do's/don'ts, an a11y line, and a variant/state legend — all
token-bound (no hardcoded hex/px). Add a metadata text node named
Doc Fingerprint holding <fp>.
- Compute
<fp> as the canonical fingerprint defined in the schema reference
(sha256 of the projected record without provenance, first 16 hex chars).
Update the manifest (fields this skill owns): set
components.meta[<Name>].doc to { path, fingerprint: <fp>, surfaces: { figmaDescription: { src: <fp>, render: <hash of the description text> }, docCard: { src: <fp>, render: <hash of the card body content> } } }. The code surfaces
(storybookMdx) are added later by storybook-chromatic-builder.
Run the standard doc-card visual-validation + post-build audit
(${CLAUDE_PLUGIN_ROOT}/references/figma-component-standards.md) after enriching
the card. docs:check runs at the code stage; at folder stage the record + Figma
surfaces are the fallback.
Step 5 — Naming as contract
Name components deterministically so Figma↔code mapping is automatic: Button ↔
Button, Avatar ↔ Avatar. This is what lets typed-component slots and the
storybook build resolve the right imports. Same discipline as icon naming —
without it, components silently diverge between Figma and code.
Step 6 — Checkpoint and hand off
Update the manifest: add each built component to components.built, and record
its components.meta[name] (status: "draft", updatedAt) to match the doc
card. (Finalize to stable happens later, in storybook-chromatic-builder.) Ensure
any component built with the toggle + manual-swap fallback is listed in
components.instanceSwapUpgradePending. Append component-builder to
completedSkills.
Upgrade pass: if components.instanceSwapUpgradePending is non-empty, re-resolve
publish state (detect-or-ask per ${CLAUDE_PLUGIN_ROOT}/references/figma-publishing.md);
only when it is confirmed published (figma.libraryPublished true via a verified
detect-or-ask, not a stale default) offer to add the typed INSTANCE_SWAP dropdowns to
those components and clear each from the list.
Offer next steps: build the code counterparts and stories
(storybook-chromatic-builder), or build a single new component end-to-end later
(the component-pipeline orchestrator).
What this skill must NOT do
- Never hardcode values that should be token/style bindings — components consume
the system.
- Never generate a redundant show/hide boolean alongside an optional slot prop.
- Never build a composite before its atomic slot targets exist.
- Never guess variant matrices — brainstorm and confirm them first.
- Never claim to publish a Figma library — publishing is a manual user step;
instruct and verify only.
- Never leave components floating on bare canvas — each goes on a token-styled
doc card arranged in an auto-layout Frame placed directly on the page (never a
Section), with the layout visually validated.
- Never lay out a component set as scattered variants — the
ComponentSet is an
auto-layout grid where variants are rows and states are columns: one row per
variant (each type, and each size, since size is a variant) stepping through
states across the columns, size groups stacked vertically (see the standards doc).
- Never ship a component with only its
default state — include the full relevant
state set (default/hover/focus/active/disabled plus applicable
loading/selected/success/error).
- Never run the component header and the component area together with no division —
every doc card segments the header from the component area with a divider line or
a distinct header surface.
- Never invent a house-style focus ring or wrap the control in a padded frame to make
room for one — derive the focus state from
project.uiFramework's real idiom, built
as a drop-shadow effect (filled controls) or an absolutely-positioned ring child
(transparent controls), per "State handling" in the standards doc.
- Never rebuild a published/consumed component set with a delete-and-recreate without
re-instancing downstream — deleting and recreating a set (e.g. to change the Button's
internal architecture) detaches every instance that referenced its variants (the
Card's footer buttons, etc.). Record which components consume which, warn before an
architectural rebuild, and re-instance the affected consumers afterward.
- Never overwrite an existing component
description or imported doc content
without reading it first and marking it provenance: imported — brownfield docs
are seeds, not blank slates.