Figma Codegen
Generate framework-aware code from Figma designs using your project's existing components and tokens.
Installation
- Make sure Claude is on your device and in your terminal.
Skills load from
~/.claude/skills/when Claude Code starts up β so you need it on your machine first. If you don't have it yet, install it once with the command below, then runclaudein any terminal to verify.One-time setupnpm i -g @anthropic-ai/claude-codeAlready have it? Skip ahead.
- Paste into Claude Code or into your terminal.
This copies the whole skill folder into
~/.claude/skills/figma-codegen-awdr74100/β the SKILL.md plus any scripts, reference docs, or templates the skill ships with. Safe default: works for every skill.Faster alternative (instruction-only skills)
Skips the clone and grabs only the SKILL.md file. Don't use this if the skill ships Python scripts, reference markdowns, or asset templates β they won't be downloaded and the skill will fail when it tries to load them.
Quick install (SKILL.md only)Sign up to copy - Restart Claude Code.
Quit and reopen Claude Code (or any other agent that loads from
~/.claude/skills/). New skills are picked up on startup. - Just ask Claude.
Skills auto-activate when your request matches the skill's description β no slash command needed. Trigger phrases live in the skill's own frontmatter; you can read them in the βWhat this skill doesβ section above.
Prefer to read the source first? Open on GitHub.
When Claude uses it
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 the user wants a Figma design turned into code β e.g. 'code this design', 'implement this frame', 'build this screen/component', 'turn this Figma into React/Vue', 'convert this design to code' β or whenever a Figma URL or the current Figma selection appears alongside a coding request. Works for full screens, single sections, or one component.
What this skill does
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/ β 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.
Workflow
Run the grounded tools against the selection, then generate β trust them over the rendered image.
-
get_design_context(detailfull,dedupeComponents: true) β the structural tree with tokens resolved to names (Primary/500,spacing/4), styles deduped intoglobalVars, and each instance'smainComponent/componentProperties. This is the layout + binding source of truth. KeepdedupeComponents: trueand 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. -
component_mapβ every Figma component grouped to a local code component with astatus(high / medium / low / unmapped),candidate.filePath, andmatchedProps.high/medium: reuse that component (import fromcandidate.filePath), don't regenerate. Never invent a component namecomponent_mapdidn'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 oftoken_map'sambiguousWith). Check which of the winningcandidate+ 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, arequiredflag, 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 backdeduped/truncated, drillget_design_contextoninstances[0].nodeIdonce β 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_apion 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.
-
token_mapβ every Figma variable joined to a project token withstatus+ref+matchedBy. A document's shared paint styles (single solid color styles β the token mechanism of pre-variables files) join the same way, markedsource: 'style'.- mapped: reference
candidate.ref(bg-primary-500,var(--color-primary-500)) β never the raw hex/pxget_design_contextresolved.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 ownprojectTokensmap 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 therefwhen 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 withcandidateslists 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): carriesbuiltin: { scale, step }β compose the utility (p-4/gap-4,leading-7,font-bold), not an arbitraryp-[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, andfromis repo-relative while Sass resolves@useagainst the importing file β so re-resolve it from where you are writing: fromsrc/components/card.scss,from: src/styles/_tokens.scssbecomes@use '../styles/tokens' as *, never the repo-relative path verbatim.as *keeps the ref as given; the project's own@usestyle may namespace it instead (@use '../styles/tokens'makes$color-primary-500intotokens.$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 onthemedCollections): the token is theme-dependent βfigmaValueis only the default mode, never the whole story. Mapped: emitcandidate.refand confirm the project token itself switches per theme (.dark/[data-theme]/prefers-color-scheme/ adark: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.
- mapped: reference
-
Export the assets grounding can't carry β logos, photos, icons have no pixels and otherwise render as grey blocks.
save_image_fillsforIMAGE-fill nodes (the original asset, not a clipped re-render),icon_mapfirst for icons (reuse curated.svgs),get_screenshotonly for the composited look. β Full asset/icon/svg/colour-contract workflow: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_diffon the section/componentnodeIdsaves a baseline (itsget_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_diffthe samenodeIdagain: it returns the per-node, per-property delta βadded/removed/changednodes with resolved values (a fill, a padding, a text string, a token rebind), each with a readablepath(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 aget_design_contextslice), thendesign_diffwithupdate: trueto accept the new design as the baseline. - Scope it by the same
nodeIdunit you coded from.no-changesmeans 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 |(orFigmaName -> path).component_mapthen returns that component assource: 'map-file', confidence 1. -
docs/figma-token-map.mdβ rows| FigmaName | ref |.token_mapreturns it asmatchedBy: ['map-file'], confidence 1. Therefis what you'd emit β a utility (bg-primary-500), avar(--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/mediumcomponent match you confirmed, anunmappedcomponent you built (record its new file), or a token that came backambiguousWith/matchedBy: ['value']/unmappedand you resolved by meaning. Skip the already-highdeterministic 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_mapreportstaleOverrides(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. - Verify visually before you call it done β render with the project's toolchain, screenshot at the
design's viewport, diff against the Figma node, fix at the source.
β
references/verify.md.
Motion (animation)
When get_design_context (full detail) tags a node with a motion summary β applied
animation-style presets, animated property fields, a timeline duration β carry it into the project's
animation mechanism (CSS @keyframes / transition, Framer Motion, GSAP, Vue <transition>) instead
of emitting a static component. get_node_motion returns the full keyframe detail when the summary
isn't enough. Dropping a frame's animation is a fidelity miss, the same class as dropping a shadow.
β references/motion.md.
Rules
- Ground every section β never eyeball a value off the screenshot. Every px size, colour,
font-size, radius, and spacing comes from
get_design_context, for every section. The screenshot is visual intent only; guessing "the easy sections" is the cardinal miss. On a page too big to ground at once, scope by sectionnodeIdβ never depth-cap the whole page, never retry an oversized call. - Reuse beats regenerate. A
high/mediumcomponent_mapcandidate must be imported and used. - Reference tokens, not literals. Emit a mapped variable's
ref; reserve raw values forunmappedgaps, and surface those gaps rather than burying them. - Carry every visual property, don't drop fidelity. Effects, per-side borders +
strokeAlign, per-corner radius, blend mode, masks, gradients, imagescaleMode, and auto-layout/grid spacing are all in the context β translate each (the catalog is inreferences/grounding.md). Dropping any is a grounding miss, not a simplification. - Export visual assets, don't fake them. A grey box or a hand-typed wordmark is a miss.
- Match the project, not a house style. Mirror the existing import style, file layout, and naming.
- Spell class names in full; don't assemble them from
&. In a language whose&concatenates (SCSS / Sass / Less / Stylus / postcss-nested).card { &__title {} }compiles to.card__titleβ a name that exists only after compilation, so the class the markup carries appears nowhere in the source and adjusting one rule means searching a fragment like__titleand reading every hit. Declare it flat at the top level instead.profile.styling.classNaming(oncomponent_map/token_map) reports the project's own habit and the project wins:ampersandβ write&__title;flator absent β write flat. βreferences/stylesheets.mdfor the descendant-selector trap that looks like the fix and isn't, and what&is still the right tool for. - Render and verify before you call it done. (See
references/verify.md.) - Never write a config file or wizard prompt; everything is inferred from the project + the tools.
Related skills
Generative Code Art
anthropics
Create algorithmic art with p5.js using randomness and interactive parameters.
UI/UX Pro Max
anthropics
Build production-grade web components and interfaces with distinctive, polished design.
Artifact Theme Toolkit
anthropics
Apply professional color and font themes to slides, docs, and web pages.
Multi-Component Web Artifacts
anthropics
Build complex React artifacts with Tailwind CSS and shadcn/ui components.