一键导入
prototype-to-figma-skill
Convert Claude Code prototypes into structured Figma design files with component mapping, state flows, and interaction annotations
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Convert Claude Code prototypes into structured Figma design files with component mapping, state flows, and interaction annotations
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
AI-powered fashion visualization and virtual try-on toolkit for consent-based garment editing and creative design workflows
Transform Markdown into paste-ready WeChat Official Account HTML with 6 themes, auto-numbering, keyword highlighting, and compliance validation
Recognizes and warns against piracy/crack tools disguised as legitimate software
Analyze and identify piracy/crack distribution repositories disguised as legitimate software tools
Analyze and understand software activation mechanisms and digital licensing patterns for educational purposes
Unlock and configure Marvelous Designer 13 for 3D garment simulation with API-driven cloth dynamics
| name | prototype-to-figma-skill |
| description | Convert Claude Code prototypes into structured Figma design files with component mapping, state flows, and interaction annotations |
| triggers | ["convert this prototype to figma","explode this into figma frames for review","create figma specs from my prototype","put this prototype in figma so the team can review it","make this reviewable by designers in figma","generate figma frames from this working prototype","turn this into a figma file with annotations","export this prototype to figma with design system components"] |
Skill by ara.so — Design Skills collection.
Converts working Claude Code prototypes into structured Figma design files by exploding interaction flows into state-by-state frames, mapping components to your Figma design system, and annotating triggers, transitions, and edge cases natively in Figma.
When a user builds a prototype in Claude Code, this skill:
User says things like:
This skill requires the Figma MCP server with these tools available:
use_figma — select target filesearch_design_system — find matching componentsget_design_context — understand file structureget_metadata — read component detailsget_screenshot — verify outputget_code_connect_map — read existing mappingsget_context_for_code_connect — understand component contextget_code_connect_suggestions — get AI suggestions for mappingssend_code_connect_mappings — batch create mappingsadd_code_connect_map — create individual mappingwhoami — get user info for file creationcreate_new_file — create new Figma fileBefore using this skill, verify Figma MCP is installed:
# Check Claude Desktop config
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | grep figma
# Or for Claude Code
cat ~/.claude/mcp_config.json | grep figma
Expected output should include @figma/mcp-server or similar.
If user provides Figma URL:
await use_figma({
url: "https://figma.com/design/abc123/MyFile"
});
If no URL provided, create new file:
// Get user info
const user = await whoami();
// Create new file
const newFile = await create_new_file({
name: `${prototypeName} — Prototype Review`,
team_id: user.team_id // or user.default_team_id
});
await use_figma({ url: newFile.url });
Read the prototype files and extract:
Example analysis structure:
interface PrototypeAnalysis {
components: {
name: string; // e.g. "PrimaryButton"
type: string; // e.g. "Button"
props: string[]; // e.g. ["label", "onClick", "disabled"]
variants: string[]; // e.g. ["default", "hover", "disabled"]
}[];
flows: {
name: string; // e.g. "Login Flow"
states: {
id: string; // e.g. "login-initial"
label: string; // e.g. "Login Form (Empty)"
components: string[];
interactions: {
trigger: string; // e.g. "Click 'Sign In'"
action: string; // e.g. "Validate and submit"
nextState: string; // e.g. "login-loading"
condition?: string; // e.g. "If fields valid"
}[];
}[];
}[];
}
For each component in the prototype:
// Search for matching DS component
const results = await search_design_system({
query: componentName,
component_type: componentType, // "Button", "Input", etc.
limit: 5
});
// If match found, get metadata
if (results.length > 0) {
const metadata = await get_metadata({
node_id: results[0].node_id
});
// Store mapping
componentMap.set(componentName, {
figma_node_id: results[0].node_id,
figma_name: results[0].name,
variants: metadata.variants,
props: metadata.properties
});
} else {
// Mark for primitive fallback
componentMap.set(componentName, {
needs_primitive_fallback: true,
primitive_type: componentType
});
}
Organize frames by flow:
Page: "Login Flow"
├─ Overview Frame (legend + TOC)
├─ State 1: Login Form (Empty)
├─ State 2: Login Form (Validation Error)
├─ State 3: Login Form (Loading)
├─ State 4: Login Success
└─ State 5: Login Error (Network)
Calculate frame positions:
const FRAME_WIDTH = 1440; // desktop, or 390 for mobile
const FRAME_HEIGHT = 900; // desktop, or 844 for mobile
const HORIZONTAL_SPACING = 200;
const VERTICAL_SPACING = 200;
function calculateFramePosition(index: number, perRow: number = 3) {
const row = Math.floor(index / perRow);
const col = index % perRow;
return {
x: col * (FRAME_WIDTH + HORIZONTAL_SPACING),
y: row * (FRAME_HEIGHT + VERTICAL_SPACING)
};
}
CRITICAL: Never call createComponent() — always use createInstance() for DS components or primitives for fallbacks.
For matched DS components:
// Create instance of DS component
const buttonInstance = await figma.createInstance({
component_id: componentMap.get("PrimaryButton").figma_node_id,
parent_id: frameId,
x: 100,
y: 200,
properties: {
variant: "default",
label: "Sign In"
}
});
For components needing primitive fallback:
// Build from primitives (example: Button)
const buttonGroup = await figma.createFrame({
name: "Button (No DS match)",
parent_id: frameId,
x: 100,
y: 200,
width: 120,
height: 40,
fills: [{ type: "SOLID", color: { r: 0.2, g: 0.4, b: 1 } }],
cornerRadius: 8
});
const buttonLabel = await figma.createText({
parent_id: buttonGroup.id,
characters: "Sign In",
fontSize: 16,
fills: [{ type: "SOLID", color: { r: 1, g: 1, b: 1 } }]
});
// Add "No DS match" badge
const badge = await figma.createFrame({
name: "⚠️ No DS match",
parent_id: buttonGroup.id,
x: -10,
y: -10,
width: 100,
height: 20,
fills: [{ type: "SOLID", color: { r: 1, g: 0.8, b: 0 } }]
});
Use native Figma Dev Mode annotations with categories:
await figma.addAnnotation({
node_id: buttonInstance.id,
category: "Interaction",
text: "Trigger: Click\nAction: Validate form fields\nNext: → Login Form (Loading) if valid, → Login Form (Error) if invalid"
});
await figma.addAnnotation({
node_id: emailInput.id,
category: "Validation",
text: "Required field\nValidation: Must be valid email format\nError message: 'Please enter a valid email'"
});
await figma.addAnnotation({
node_id: errorBanner.id,
category: "Error Handling",
text: "Shown when: Network request fails\nDuration: Persistent until dismissed\nDismiss: Click X icon or retry"
});
Categories to use:
Interaction — clicks, hovers, focusNavigation — route changes, deep linksValidation — input rules, error statesError Handling — error scenarios, recoveryData Flow — API calls, state updatesEdge Cases — empty states, loading, timeoutsCreate connector arrows between frames:
await figma.createConnector({
start_node_id: state1Frame.id,
end_node_id: state2Frame.id,
stroke_weight: 2,
stroke_color: { r: 0.5, g: 0.5, b: 0.5 },
label: "Click 'Sign In'"
});
First frame in each flow page:
const overviewFrame = await figma.createFrame({
name: "📋 Overview",
width: FRAME_WIDTH,
height: FRAME_HEIGHT,
fills: [{ type: "SOLID", color: { r: 0.98, g: 0.98, b: 0.98 } }]
});
// Add title
await figma.createText({
parent_id: overviewFrame.id,
characters: "Login Flow — Prototype Review",
fontSize: 32,
fontWeight: 700,
y: 40
});
// Add legend
await figma.createText({
parent_id: overviewFrame.id,
characters: `Legend:
• 🟦 Frame = One UI state
• ➡️ Arrow = User action / transition
• 📝 Annotation = Interaction detail
• ⚠️ Badge = No design system match (built from primitives)`,
fontSize: 16,
y: 120
});
// Add TOC
await figma.createText({
parent_id: overviewFrame.id,
characters: `States in this flow:
1. Login Form (Empty)
2. Login Form (Validation Error)
3. Login Form (Loading)
4. Login Success
5. Login Error (Network)`,
fontSize: 16,
y: 250
});
// Add open questions
await figma.createText({
parent_id: overviewFrame.id,
characters: `Open questions for reviewers:
• Should "Forgot Password" link to modal or new page?
• Do we show loading spinner in button or overlay entire form?
• How long should success message persist?`,
fontSize: 16,
y: 450,
fills: [{ type: "SOLID", color: { r: 0.8, g: 0.4, b: 0 } }]
});
If user wants Code Connect integration:
// Get existing mappings
const existingMaps = await get_code_connect_map({
file_key: fileKey
});
// Get suggestions for unmapped components
const suggestions = await get_code_connect_suggestions({
component_id: buttonComponentId,
code_snippet: `<Button variant="primary" onClick={handleClick}>Sign In</Button>`
});
// Create mappings
const mappings = componentMap.entries().map(([codeName, figmaInfo]) => ({
figma_node_id: figmaInfo.figma_node_id,
code_location: `src/components/${codeName}.tsx`,
code_snippet: getComponentSnippet(codeName),
framework: "react" // or "vue", "svelte", etc.
}));
await send_code_connect_mappings({
mappings: mappings
});
// Take screenshot of output
const screenshot = await get_screenshot({
node_id: pageId,
scale: 0.5 // overview scale
});
// Present to user
return `✅ Prototype converted to Figma!
File: ${figmaFileUrl}
Created:
• ${flowCount} flows
• ${frameCount} state frames
• ${annotationCount} interaction annotations
• ${componentMatchCount} design system components used
• ${primitiveFallbackCount} components built from primitives (flagged with ⚠️)
${codeConnectEnabled ? `Code Connect: ${mappingCount} mappings created` : ''}
Open questions for reviewers:
${openQuestions.join('\n')}
[Screenshot attached]`;
function detectFramework(prototypeFiles: string[]): string {
const hasReact = prototypeFiles.some(f =>
f.includes('import React') || f.includes('from "react"')
);
const hasVue = prototypeFiles.some(f =>
f.includes('<template>') || f.includes('from "vue"')
);
const hasSvelte = prototypeFiles.some(f =>
f.includes('<script>') && f.endsWith('.svelte')
);
if (hasReact) return 'react';
if (hasVue) return 'vue';
if (hasSvelte) return 'svelte';
return 'html'; // vanilla
}
Group very small interactions (hover, focus) into a single frame with annotations:
function shouldGroupInteractions(interactions: Interaction[]): boolean {
// If all interactions are micro-interactions (hover, focus, blur)
const microTypes = ['hover', 'focus', 'blur', 'mouseover', 'mouseout'];
return interactions.every(i => microTypes.includes(i.type));
}
// Instead of 5 frames for hover states, create 1 frame with annotations:
await figma.addAnnotation({
node_id: button.id,
category: "Interaction",
text: `Hover: Background darkens to #1a3d8f
Focus: 2px blue outline appears
Click: Ripple animation, navigate to /dashboard`
});
function explodeResponsiveStates(state: UIState): UIState[] {
if (!state.hasResponsiveVariants) return [state];
return [
{ ...state, viewport: 'mobile', frameWidth: 390, frameHeight: 844 },
{ ...state, viewport: 'tablet', frameWidth: 768, frameHeight: 1024 },
{ ...state, viewport: 'desktop', frameWidth: 1440, frameHeight: 900 }
];
}
async function createPrimitiveButton(
parent_id: string,
x: number,
y: number,
label: string,
variant: 'primary' | 'secondary' = 'primary'
) {
const colors = {
primary: { bg: { r: 0.2, g: 0.4, b: 1 }, fg: { r: 1, g: 1, b: 1 } },
secondary: { bg: { r: 0.95, g: 0.95, b: 0.95 }, fg: { r: 0.2, g: 0.2, b: 0.2 } }
};
const group = await figma.createFrame({
name: `Button: ${label} (No DS match)`,
parent_id,
x,
y,
width: Math.max(120, label.length * 10),
height: 40,
fills: [{ type: "SOLID", color: colors[variant].bg }],
cornerRadius: 8,
paddingLeft: 16,
paddingRight: 16
});
await figma.createText({
parent_id: group.id,
characters: label,
fontSize: 16,
fontWeight: 500,
fills: [{ type: "SOLID", color: colors[variant].fg }],
textAlignHorizontal: "CENTER",
textAlignVertical: "CENTER"
});
// Add badge
await figma.createFrame({
name: "⚠️ No DS match",
parent_id: group.id,
x: -12,
y: -12,
width: 110,
height: 24,
fills: [{ type: "SOLID", color: { r: 1, g: 0.8, b: 0 } }],
cornerRadius: 4
});
return group;
}
Cause: Figma MCP server not installed or not running.
Solution:
# Install Figma MCP
npx @figma/mcp-server init
# Or check Claude Desktop config
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json
Cause: Skill is calling createComponent() instead of using instances or primitives.
Solution: NEVER call createComponent(). Always:
createInstance() for DS componentscreateFrame, createText, etc.) for fallbacksCause: Target file has no linked libraries, or component naming mismatch.
Solution:
search_design_system({ query: "btn primary" }) instead of exact "PrimaryButton"Cause: Exploding every micro-interaction into separate frames.
Solution: Group hover/focus/blur into single frame with annotations (see pattern above).
Cause: User not in Dev Mode, or annotations have no category.
Solution: Always specify category. Remind user to switch to Dev Mode in Figma.
Cause: Invalid code snippet format, or component not in file.
Solution:
// Get context first
const context = await get_context_for_code_connect({
component_id: nodeId
});
// Then create mapping with valid snippet
await add_code_connect_map({
figma_node_id: nodeId,
code_location: "src/components/Button.tsx",
code_snippet: `import { Button } from "./Button";\n\n<Button variant="primary">Click me</Button>`,
framework: "react"
});
This skill produces different outputs based on client capabilities:
Tier 1 (Full build in Figma): Claude Code, Claude Desktop, Cursor, VS Code, Copilot CLI, Augment Code, Factory, Firebender, Codex
Tier 2 (Spec document): Android Studio, Gemini CLI, Kiro, Amazon Q, Openhands
Tier 3 (Build only): Replit
Check available tools at runtime:
const hasCodeConnect = tools.includes('get_code_connect_suggestions');
const canBuildInFigma = tools.includes('use_figma');
This skill does not require API keys — it uses Figma MCP tools which handle authentication via OAuth.
Users must have Figma account connected to their Claude client.
use_figma() before any other Figma operations// User: "Put this login prototype in Figma"
// 1. Create new file (no URL provided)
const user = await whoami();
const file = await create_new_file({
name: "Login Prototype — Review",
team_id: user.team_id
});
await use_figma({ url: file.url });
// 2. Analyze prototype
const analysis = analyzePrototype(prototypeFiles);
// → { flows: [{ name: "Login Flow", states: [...] }], components: [...] }
// 3. Map components
for (const comp of analysis.components) {
const results = await search_design_system({
query: comp.name,
component_type: comp.type
});
if (results.length > 0) {
componentMap.set(comp.name, results[0]);
} else {
componentMap.set(comp.name, { primitive: true, type: comp.type });
}
}
// 4. Create page
const page = await figma.createPage({ name: "Login Flow" });
// 5. Create overview frame
const overview = await createOverviewFrame(page.id, analysis.flows[0]);
// 6. Create state frames
for (let i = 0; i < analysis.flows[0].states.length; i++) {
const state = analysis.flows[0].states[i];
const pos = calculateFramePosition(i + 1); // +1 to skip overview
const frame = await figma.createFrame({
name: `State ${i + 1}: ${state.label}`,
parent_id: page.id,
x: pos.x,
y: pos.y,
width: 1440,
height: 900
});
// Build components
for (const compName of state.components) {
const mapping = componentMap.get(compName);
if (mapping.primitive) {
await createPrimitiveButton(frame.id, 100, 100, compName);
} else {
await figma.createInstance({
component_id: mapping.figma_node_id,
parent_id: frame.id
});
}
}
// Add annotations
for (const interaction of state.interactions) {
await figma.addAnnotation({
node_id: frame.id,
category: "Interaction",
text: `${interaction.trigger} → ${interaction.action}`
});
}
// Add flow arrow to next state
if (i < analysis.flows[0].states.length - 1) {
await figma.createConnector({
start_node_id: frame.id,
end_node_id: nextFrameId
});
}
}
// 7. Screenshot and present
const screenshot = await get_screenshot({ node_id: page.id });
return `✅ Prototype in Figma: ${file.url}`;
This skill is part of the Design Skills collection at ara.so. For issues or contributions, see the GitHub repo.