figma-codegen
Generate framework-aware code from a Figma design. Reads the project's stack profile and emits code matching the existing framework (React/Vue/Svelte/Next/etc.) and styling (Tailwind/CSS/CSS-in-JS), reusing existing components and design tokens instead of regenerating from scratch. Triggers whenever
By awdr74100 · 672 installs
npx skills add awdr74100/figwright --skill figma-codegen
Source repository · Upstream listing
figma codegen
Turn a Figma selection into code that looks like the rest of the project: reuse the components and
tokens that already exist, only build what's genuinely missing. The grounded tools do the heavy
lifting so you are not guessing from a screenshot. This file is the router; deep detail lives in
[ references/ ](./references) — load a reference when its step is in play.
When to use
The user pastes a Figma URL/selection and asks for code ("code this", "build this component").
The user wants to extend an existing component to match a Figma frame.
More than one Figma file open
Every tool result says so when it applies, so you do not have to check up front: with two or more
files connected and none claimed, results carry a MORE THAN ONE FIGMA FILE IS OPEN block naming
them. It matters because calls otherwise follow whichever file the user last touched, which changes
when they switch tabs — so a task can read one file and write to another and report success.
When you see it, claim the file before doing anything else: list files names every connected file,
use file({ fileName }) claims one for this session ( use file({ sessionId }) when two open files
share a name — Figma allows that, and x (Copy) is how it happens). Every later call then reaches
that file whatever is in front, including while its tab sits in the background. If the user has not
said which file, ask — do not guess from the names.
Workflow
Run the grounded tools against the selection, then generate — trust them over the rendered image.
1. get design context (detail full , dedupeComponents: true ) → the structural tree with
tokens resolved to names ( Primary/500 , spacing/4 ), styles deduped into globalVars , and each
instance's mainComponent / componentProperties . This is the layout + binding source of truth.
Keep dedupeComponents: true and don't depth limit a subtree you'll build from.
→ How to read the tree, the per node fidelity catalog (effects, per side borders, stroke align,
per corner radius, blend, masks, gradients, image fit, auto layout/grid, aspect ratio,
scroll/sticky, stack & stroke space), Dev Mode annotations as ground truth, and grounding a page
too big for one call: [ references/grounding.md ](./references/grounding.md).
2. component map → every Figma component grouped to a local code component with a status
(high / medium / low / unmapped), candidate.filePath , and matchedProps .
high / medium : reuse that component (import from candidate.filePath ), don't regenerate.
Never invent a component name component map didn't report.
candidate.ambiguousWith (a capped list of { name, filePath } ): the Figma name matched two or
more code components nearly equally and the join couldn't confidently pick — a verify me pick,
not a confident reuse (the analogue of token map 's ambiguousWith ). Check which of the winning
candidate + these runner ups is the right component for this context before importing (a wrong
reuse is a silent visual bug), then record the confirmed one in the map file so the next run is
certain. Absent when the pick was unambiguous.
Wire each entry's instances[].props (resolved variant / boolean / text values) onto the reused
component — one element per instance, with its own props.
candidate.unmatchedProps : Figma axes the component has no prop for (a leading icon, a required
flag, an active state) → surface as component extension TODOs, never fake them with ad hoc markup.
unmapped : build it new in the project's style. For a repeated unmapped component
( instanceCount 1 ), build from its first instance's subtree; if that came back
deduped / truncated , drill get design context on instances[0].nodeId once — don't rebuild a
repeated component by eye.
When you're emitting a component's own definition (its prop types, not just rendering it),
get component api on the component/instance returns the full property API — every VARIANT option
and each BOOLEAN/TEXT/INSTANCE SWAP prop with its default — so the prop space is grounded, not
inferred from the instances you happened to see.
3. token map → every Figma variable joined to a project token with status + ref + matchedBy .
A document's shared paint styles (single solid color styles — the token mechanism of
pre variables files) join the same way, marked source: 'style' .
mapped: reference candidate.ref ( bg primary 500 , var( color primary 500) ) — never the raw
hex/px get design context resolved. matchedBy: ['name'] on a colour (value drifted): use it
but flag the mismatch.
candidate.ambiguousWith (several project tokens share that exact value and the name couldn't
split them): a capped, verify me pick — choose the semantically right sibling for the context
(or keep the value and flag the gap), never treat it as a confirmed reuse.
On a document with few or no variables (most real world files), get design context 's own
projectTokens map is the fallback: any raw color in the payload that exactly equals a project
token's value is annotated there ( { " 6266F0": { ref, name, matchedBy: ["value"] } } ). Before
hardcoding a hex, look it up and emit the ref when it fits the context semantically.
matchedBy: ["value"] marks the entry as name blind value equality evidence — a hypothesis to
verify, not a resolved binding: an entry with candidates lists same value tokens to choose
between by meaning, a semantically wrong token is worse than the raw value (keep the raw
value and note the gap instead), and a bound Figma variable always outranks a raw value match.
framework builtin (a built in scale step of the project's utility framework — Tailwind or
UnoCSS — e.g. spacing/4 , line height/7 , weight/Bold ): carries builtin: { scale, step }
— compose the utility ( p 4 / gap 4 , leading 7 , font bold ), not an arbitrary
p [16px] . This is not a gap.
from (a SCSS variable): the ref does not resolve on its own. The file you write must
import the declaring file, and from is repo relative while Sass resolves @use against
the importing file — so re resolve it from where you are writing: from
src/components/card.scss , from: src/styles/ tokens.scss becomes
@use '../styles/tokens' as , never the repo relative path verbatim. as keeps the ref as
given; the project's own @use style may namespace it instead ( @use '../styles/tokens' makes
$color primary 500 into tokens.$color primary 500 ). Emitting the ref without the import is
a compile error , not a style nit.
unmapped : use the value but call out the gap (offer to add it to the token source); don't
hardcode silently.
figmaModes ( { Light: …, Dark: … } , with the file's theme axes on themedCollections ): the
token is theme dependent — figmaValue is only the default mode, never the whole story.
Mapped: emit candidate.ref and confirm the project token itself switches per theme ( .dark /
[data theme] / prefers color scheme / a dark: story); if the project defines only one
value, wire the other mode's value into that mechanism or surface a theme gap. Unmapped: the
default mode is the base, other modes ride the project's dark mode mechanism ( dark: variants /
prefers color scheme ). Themes encoded without native modes — paired collections or name
groups ( Color/Light/ + Color/Dark/ , a plan limited workaround) — get the same treatment.
4. Export the assets grounding can't carry — logos, photos, icons have no pixels and otherwise
render as grey blocks. save image fills for IMAGE fill nodes (the original asset, not a
clipped re render), icon map first for icons (reuse curated .svg s), get screenshot only for
the composited look.
→ Full asset/icon/svg/colour contract workflow:
[ references/assets and icons.md ](./references/assets and icons.md).
Then emit code in the detected stack (the profile comes back on component map / token map ; you do
not call analyze project yourself): compose the reused components, wrap unmapped pieces, and apply
token references for colour/spacing/radius/typography.
Keep code in sync as the design changes
Codegen is rarely one shot — the design keeps moving. To make the second pass an incremental edit
instead of a regeneration:
After you generate , design diff on the section/component nodeId saves a baseline (its
get design context ) under .figwright/snapshots/ . Committing that file lets teammates share the
baseline; the tool never touches git.
When asked to re sync ("the design changed, update the component"), design diff the same
nodeId again: it returns the per node, per property delta — added / removed / changed nodes
with resolved values (a fill, a padding, a text string, a token rebind), each with a readable path
( Card / Header / Title ). Edit only the code those nodes map to; don't regenerate the screen.
Ground each changed value the usual way (it's a get design context slice), then design diff with
update: true to accept the new design as the baseline.
Scope it by the same nodeId unit you coded from. no changes means the design is untouched since
the baseline — nothing to do.
Record verified mappings (so the next run reuses, not re guesses)
The joins re derive from scratch every run. A mapping you confirmed — reused a component and it
rendered right, resolved an ambiguous colour to the semantically correct token — is worth recording
so the next run treats it as ground truth instead of re guessing. Two append only map files, each read
back by its join as highest authority:
docs/figma component map.md — rows FigmaName code/path (or FigmaName path ).
component map then returns that component as source: 'map file' , confidence 1.
docs/figma token map.md — rows FigmaName ref . token map returns it as
matchedBy: ['map file'] , confidence 1. The ref is what you'd emit — a utility ( bg primary 500 ),
a var( color primary 500) , or the bare token name.
A recorded row OVERRIDES the fuzzy join on every future run — so record proof, not a pick. The
gate is your own verify step (§ Responsive & verify): only after you rendered the result and it
matched the Figma node do you record the mapping you proved . A wrong row doesn't fail loudly — it
silently mis maps that component/token in every future generation until a human notices, which is
worse than no record at all. If you're not certain, don't record it; a re guess next run is
recoverable, a confidently wrong recorded row is not.
Record only the mappings the join was unsure of — a low / medium component match you
confirmed, an unmapped component you built (record its new file), or a token that came back
ambiguousWith / matchedBy: ['value'] / unmapped and you resolved by meaning. Skip the
already high deterministic matches; they re derive correctly and a row for them is just noise.
These files are committed project docs — treat a row like a line of code you're asserting is
correct , not a scratch note. One figma name → one target per row; keep them reviewable.
Keep the files healthy. When component map / token map report staleOverrides (a recorded
target that no longer resolves — the file/token was renamed or deleted), the join has already
degraded to the fuzzy result; fix that row to the new target or delete it.
Responsive & verify
Responsive by default — root is w full , never the artboard's fixed width; ground breakpoints
from the file's other width frames. → [ references/responsive.md ](./references/responsive.md).
Verify visually before you call it d