// Extracts the ENTIRE design system from an existing codebase (repo) and exports it to a single runnable Figma plugin script that automatically creates tokens, components, AND frames. Always use this skill when the user wants to: move their design from code to Figma/Pencil, extract design tokens from a repo, sync a codebase design to a design tool, generate a design system from existing code, reverse-engineer their app design, export colors/typography/spacing/components/pages from their app, or get their entire app design into Figma automatically. Triggers on phrases like: "design to figma", "extract design", "design tokens", "code to figma", "export design", "design system from repo", "reverse design", "get my design into figma", "components to figma", "frames to figma", "whole design to figma".
[HINT] Download the complete skill directory including SKILL.md and all related files
name
design-extractor
description
Extracts the ENTIRE design system from an existing codebase (repo) and exports it to a single runnable Figma plugin script that automatically creates tokens, components, AND frames. Always use this skill when the user wants to: move their design from code to Figma/Pencil, extract design tokens from a repo, sync a codebase design to a design tool, generate a design system from existing code, reverse-engineer their app design, export colors/typography/spacing/components/pages from their app, or get their entire app design into Figma automatically. Triggers on phrases like: "design to figma", "extract design", "design tokens", "code to figma", "export design", "design system from repo", "reverse design", "get my design into figma", "components to figma", "frames to figma", "whole design to figma".
Design Extractor Skill
Takes the entire design from a repo and generates a single JS script that the user
pastes into the Figma Console. The script automatically creates tokens, components and frames.
First time on a new project? Run setup-extractor.md (in this folder) to calibrate
the skill for your framework and styling approach before generating the script.
End result for the user
1. Claude Code runs the skill against the repo
2. Generates a single figma-import.js script
3. User opens Figma
4. Menu → Plugins → Development → Open Console
5. Paste the script → Enter
6. The entire design is created automatically in Figma
One paste. One Enter. Done.
What the skill does
Given a repo, you will:
Analyze the codebase and identify all design-related information
Text node — resolve android:textAppearance via styles.xml for size + weight
MaterialToolbar
Nav bar — read android:background, android:height
Resolve @color/name and @dimen/name references using the extracted token maps before setting values.
For android:backgroundTint="@color/color_primary" → use tokens.colors.primary.
Button variants: if only one layout file exists, infer secondary (transparent + stroke) and ghost (transparent, no stroke) from Material Design convention — note this as inferred in design-system-summary.md.
Read the reference file for full guide:
→ references/component-patterns.md
Step 4: Extract frames (pages/views)
Identify all routes and analyze their layouts.
Next.js App Router: scan app/**/page.tsx
Next.js Pages: scan pages/**/*.tsx
HTML: scan all .html files
SwiftUI: scan all *View.swift files
React Router: read App.tsx / router.tsx
Kotlin/Android: scan *Activity.kt files — each Activity = one screen. Read its paired res/layout/activity_*.xml for layout structure. Mobile frame size: 390×844px.
Read the reference file for full guide:
→ references/frame-generator.md
Step 5: Generate the combined script
ALWAYS generate a single combined script — not three separate ones.
Structure for figma-import.js:
// === DESIGN IMPORT – [Project name] ===// Generated by Claude Code [date]// Run in: Figma → Plugins → Development → Open Consoleconst tokens = { colors: {...}, typography: {...}, spacing: {...} };
functionsolidColor(hex) { ... }
asyncfunctionloadFonts() {
// Collect every unique family+style from tokens.typography, then load them allconst toLoad = newSet();
for (const t ofObject.values(tokens.typography)) {
toLoad.add(JSON.stringify({ family: t.family, style: weightToStyle(t.weight) }));
}
// Always include fallback weights so helper text nodes never failfor (const family of [...newSet(Object.values(tokens.typography).map(t => t.family))]) {
for (const style of ["Regular", "Medium", "Semi Bold", "Bold"]) {
toLoad.add(JSON.stringify({ family, style }));
}
}
for (const entry of toLoad) {
try { await figma.loadFontAsync(JSON.parse(entry)); } catch {}
}
}
asyncfunctioncreateTokenStyles() { ... }
const components = [ ... ];
asyncfunctioncreateComponents() { ... }
const pages = [ ... ];
asyncfunctionbuildFrames() { ... }
// Top-level await — NO async IIFE (crashes Figma console before fonts load)try {
console.log("⏳ Starting design import...");
awaitloadFonts();
awaitcreateTokenStyles();
awaitcreateComponents();
awaitbuildFrames();
console.log("🎉 SUCCESS: Design imported!");
} catch (err) {
console.error("❌ ERROR:", err);
}
See full templates in:
→ references/figma-plugin-template.md
→ references/frame-generator.md
Step 6: Deliver
Always deliver:
figma-import.js — the combined script, ready to run
design-system-summary.md — what was found and any gaps
Three-line instructions on how to run it in Figma
Step 7: Self-validate before delivering
After generating figma-import.js, run the validator:
If any checks fail: fix the script, re-run the validator. Do not deliver until exit code is 0.
Use the checklist below as a fix guide when the validator reports a failure:
Rule violations (instant fail — fix before delivering)
Rule 1 – Page count: Does the script create exactly 2 pages (🧩 Components and 📐 Frames)? Search output for figma.createPage(). Count must be exactly 2.
Rule 2 – No placeholders: Does every fills value come from tokens.colors.*? Search for hardcoded hex strings not assigned to a token first.
Rule 3 – Token connections: Does every frame section reference tokens.colors.* rather than a literal hex?
Rule 4 – Mobile + desktop frames: For each entry in pages[], is there both a 390px mobile and a 1440px desktop frame?
Rule 5 – Font mapping: Does the script contain any of: "SF Pro", "-apple-system", "BlinkMacSystemFont", "system-ui", "system"? If yes, replace with "Inter".
Rule 6 – lineHeight unit: Search for unit: "MULTIPLIER". Must return zero matches. All lineHeight values must use "PIXELS", "PERCENT", or "AUTO".
Rule 7 – Top-level await: Search for (async () => { or (async() => {. Must return zero matches.
Rule 8 – setCurrentPageAsync: Search for figma.currentPage = (assignment, not .name =). Must return zero matches.
Rule 9 – No figma.notify / figma.closePlugin: Search for figma.notify( and figma.closePlugin(. Must both return zero matches.
Rule 10 – String quoting: Any string value sourced from extracted repo data (page titles, component names, button text) uses single-quote outer delimiter.
Structure checks
runPhase helper is present and all four phases use it
Each component loop and each frame loop has its own per-item try-catch
Progress console.log messages present at start of each phase ([1/4], [2/4], [3/4], [4/4])
If any check fails: fix the generated script, then re-run the checklist from the top.
Only deliver once all boxes can be checked.
Framework compatibility
Framework
Tokens
Components
Frames
Next.js (Tailwind)
✅ Automatic
✅ Automatic
✅ Automatic
React + CSS Modules
✅ Good
✅ Good
✅ Good
HTML/CSS
✅ CSS vars
✅ Manual scan
✅ Per .html file
SwiftUI
⚠️ Color extensions
✅ Views
✅ Scenes
Vue
✅ Automatic
✅ Automatic
✅ Automatic
Kotlin/Android (XML Views)
✅ colors.xml + dimens.xml
✅ Layout XML
✅ Per Activity
C++ / Qt
⚠️ Manual
⚠️ .ui files
⚠️ Widget tree
Claude Code adapts the extraction per framework automatically.
Key principles
Never destructive — read only, never modify the repo
Assume gracefully — if unsure of a semantic name, use the technical value
Flag gaps — clearly state what could not be extracted automatically
Keep it portable — output must work without knowing the specific repo
CRITICAL RULES — Always follow these
These are hard rules learned from real usage. Never violate them.
Rule 1: NEVER create multiple Figma pages
Figma's free plan only allows 3 pages. Creating one page per route will immediately hit this limit.
ALWAYS put everything on maximum 2 pages:
Page 1: "🧩 Components" → all components side by side
Page 2: "📐 Frames" → ALL page frames side by side on one canvas
Frames go next to each other horizontally with 80px gap — never on separate Figma pages.
// WRONG ❌ — creates a new Figma page per routeconst figmaPage = figma.createPage();
// CORRECT ✅ — all frames on one page, positioned with xOffsetlet xOffset = 0;
for (const pageData of pages) {
const frame = figma.createFrame();
frame.x = xOffset;
frame.y = 0;
xOffset += (frame.width || 390) + 80;
figma.currentPage.appendChild(frame);
}
Rule 2: NEVER use placeholder boxes for frame content
Frames must use actual colors, spacing and structure from the extracted design — not labeled placeholder rectangles.
// WRONG ❌const box = figma.createFrame();
box.fills = [solidColor("#EFF6FF")]; // generic placeholder color// adds text "Search Bar" as a label// CORRECT ✅const heroSection = figma.createFrame();
heroSection.fills = [solidColor(tokens.colors["primary"])]; // actual brand color
heroSection.layoutMode = "VERTICAL";
heroSection.paddingLeft = heroSection.paddingRight = 24;
heroSection.paddingTop = heroSection.paddingBottom = 32;
heroSection.itemSpacing = 16;
// real text nodes with actual typography tokens
Rule 3: Always connect frames to extracted tokens
Every color, font and spacing value in a frame must come from the extracted tokens — never use hardcoded defaults like #3B82F6 if the repo has a different primary color.
Rule 4: Generate mobile-first frames
For each route generate:
Mobile frame (390px) — always first, primary for app projects
Desktop frame (1440px) — placed 80px to the right of mobile
Rule 5: Map system fonts to Figma-loadable fonts
Figma cannot load OS-level system fonts. Any system font reference found in the repo must be mapped to the nearest Figma-available equivalent before generating the script.
Source font
Map to
SF Pro, SF Pro Display, SF Pro Text
"Inter"
-apple-system, BlinkMacSystemFont
"Inter"
.font(.system(...)) (SwiftUI)
"Inter"
Roboto
"Roboto" (available in Figma)
system-ui
"Inter"
Android: sans-serif / Roboto
"Roboto"
Add a comment in the generated script noting the mapping so users know why the font differs from their app:
// Note: SF Pro (iOS system font) mapped to Inter — Figma cannot load device fonts
Rule 6: lineHeight must use PIXELS, PERCENT or AUTO — never MULTIPLIER
Figma does not accept MULTIPLIER as a lineHeight unit. Always use one of the three valid formats:
⚙️ Figma Script Generation Rules for Developer Console
When generating JavaScript meant to run directly in the Figma Developer Console, follow these rules to prevent runtime crashes and deprecation warnings.
Rule 6: NEVER use async IIFEs — use top-level await
The Figma console supports top-level await natively. Wrapping code in (async () => { ... })() causes the plugin environment to close before async operations finish, crashing the script.
Rule 8: Use console.log / console.error — NOT figma.notify() or figma.closePlugin()
figma.notify() relies on async timers that can crash when the console environment shuts down. figma.closePlugin() terminates the environment mid-run when called from the console.
This applies at EVERY point in the script — including the very last line. Do not add figma.notify() after the try/catch block as a "success toast".
// WRONG ❌ — anywhere in the script, including the final line
figma.notify("🎉 Done!", { timeout: 5000 });
figma.closePlugin();
// CORRECT ✅console.log("🎉 SUCCESS: Design imported!");
Rule 9: Use single-quoted outer delimiters when string content may contain double quotes
If generated string content (labels, titles, names from the repo) could contain double-quote characters, use single quotes as the outer delimiter. A double quote inside a double-quoted JS string will break the parser with "missing ) after argument list".
// WRONG ❌ — breaks if the string contains "
t.characters = "Which actor plays the main character in "TheDarkKnight"?";
// CORRECT ✅ — safe regardless of content
t.characters = 'Which actor plays the main character in "The Dark Knight"?';
Apply this whenever the string value comes from extracted repo data — component names, page titles, label text, anything that wasn't written by hand.
Correct main block template
Every generated script must follow this structure:
Component folder structure (where your components live)
Code generation rules (naming conventions, file format, output structure)
SwiftUI Project Notes (learned from SceneIt)
When extracting from a SwiftUI project:
Colors: Read Color+Extensions.swift or Assets.xcassets color set JSON files. Dark-mode variants are usually the primary appearance.
Flat token object: Use a flat const colors = { key: "#hex" } rather than a nested tokens.colors object — the script helper functions (sc, hGrad, vGrad) reference colors directly.
Typography: SwiftUI .font(.system(size:weight:)) maps to Inter in Figma. Extract all unique sizes and weights into a semantic token table.
Spacing constants: Look for enum Spacing or struct Spacing with static let values.
Corner radius: Look for enum CornerRadius or extension on CGFloat.
Views → Frames: Each *View.swift file is one frame. Map ZStack/VStack/HStack to Figma frame layout direction.
Custom shapes (Shape protocol): Cannot be reproduced exactly — approximate with nearest available Figma shape and note the limitation in design-system-summary.md.
Gradient helpers: Always generate hGrad(c1, c2) and vGrad(c1, c2) helpers in the script when the app uses gradients.
txt() helper: Always generate an async txt(chars, size, weight, hex, alpha) helper to avoid repeating font/fill setup on every text node.