npx claudepluginhub anthropics/claude-plugins-official --plugin figmaThis skill uses the workspace's default tool permissions.
Use this skill to create or update **screens, views, and multi-section UI containers** in Figma by **reusing the published design system** — components, variables, and styles — rather than drawing primitives with hardcoded values. This includes full pages, modals, dialogs, drawers, sidebars, panels, and any composed view with multiple sections. The key insight: the Figma file likely has a publi...
Builds or updates Figma screens from code or descriptions by discovering design system components, variables, styles and assembling them section-by-section.
Orchestrates phased Figma design system builds from codebases: extracts tokens/components, creates variables/modes/theming, sets up foundations and structure.
Generates Figma components or screens from requests like 'make a button' or 'design a settings page'. Compiles CSpec to scene graph, executes via MCP, and verifies output.
Share bugs, ideas, or general feedback.
Use this skill to create or update screens, views, and multi-section UI containers in Figma by reusing the published design system — components, variables, and styles — rather than drawing primitives with hardcoded values. This includes full pages, modals, dialogs, drawers, sidebars, panels, and any composed view with multiple sections. The key insight: the Figma file likely has a published design system with components, color/spacing variables, and text/effect styles that correspond to the codebase's UI components and tokens. Find and use those instead of drawing boxes with hex colors.
MANDATORY: You MUST also load figma-use before any use_figma call. That skill contains critical rules (color ranges, font loading, etc.) that apply to every script you write.
Always pass skillNames: "figma-generate-design" when calling use_figma as part of this skill. This is a logging parameter — it does not affect execution.
When building a screen from a web app that can be rendered in a browser, the best results come from running both approaches in parallel:
generate_figma_design to capture a pixel-perfect screenshot of the running web appgenerate_figma_design capture. The capture provides the exact spacing, sizing, and visual treatment to aim for, while your use_figma output has proper component instances linked to the design system. If the capture contains images, transfer them to your use_figma output by copying imageHash values from the capture's image fills (see Step 5 for details).generate_figma_design output — it was only used as a visual reference.This combines the best of both: generate_figma_design gives pixel-perfect layout accuracy, while use_figma gives proper design system component instances that stay linked and updatable.
This parallel workflow is MANDATORY when the source contains images. The use_figma Plugin API cannot fetch external image URLs — it can only set image fills by copying imageHash values from nodes already in the file. generate_figma_design rasterizes all visible images into Figma, providing the hashes you need. If you skip the capture when images are present, image frames will be left blank.
For non-web apps (iOS, Android, etc.) or when updating existing screens, use the standard workflow below.
Follow these steps in order. Do not skip steps.
Hard gates — forbidden shortcuts:
- Forbidden:
search_design_systemfor component keys until 2a-i is complete and 2a-ii is attempted or logged N/A (e.g. "empty file, no existing screens").- Forbidden: Any
use_figmacall that mutates the canvas (Step 3+) until all Step 2 rows in the checklist below are filled in.
Before touching Figma, understand what you're building:
<img>, <Image>, background images, product photos, avatars, icons loaded from URLs). If it does and this is a web app, you must run the parallel generate_figma_design capture workflow — start it immediately alongside Step 2 so the capture runs while you discover components. See "Parallel Workflow with generate_figma_design" above.You need three things from the design system: components (buttons, cards, etc.), variables (colors, spacing, radii), and styles (text styles, effect styles like shadows). Don't hardcode hex colors or pixel values when design system tokens exist.
2a-i — REQUIRED: Check Code Connect for needed components. Starting from the component list you built in Step 1, check whether each component has a Code Connect file in the codebase. Code Connect files live next to the component source and are named by platform:
*.figma.ts, *.figma.js*.figma.tsx.kt files containing @FigmaConnect.swift files containing FigmaConnectFor each component you need (e.g., Button, Card, Input), search for its Code Connect file — glob or grep by component name (e.g., **/Button.figma.tsx, **/Card.figma.ts). Only read files that match components you actually need.
From each matching Code Connect file, extract the Figma component URL. Parse fileKey and nodeId from the URL (convert hyphens to colons: 123-456 → 123:456). Then resolve component keys via use_figma:
Example: Code Connect file contains // url=https://figma.com/design/ABC123/File?node-id=609-35535. Parse fileKey = ABC123, nodeId = 609:35535. Run use_figma against the library file (fileKey ABC123, not the target file) to resolve the key:
const node = await figma.getNodeByIdAsync("609:35535");
const set = node?.parent?.type === "COMPONENT_SET" ? node.parent : node;
return { componentKey: set.key };
Batch multiple lookups in a single call. Use the returned keys with importComponentSetByKeyAsync() in Step 4.
Mark resolved components. If all components are resolved, skip 2a-ii and 2a-iii. If none of the needed components have Code Connect files, proceed to 2a-ii.
2a-ii — REQUIRED if unresolved components remain: Inspect existing screens. Check if the target file already contains screens using the same design system. A single use_figma call that walks an existing frame's instances gives you an exact, authoritative component map:
const frame = figma.currentPage.findOne(n => n.name === "Existing Screen");
const uniqueSets = new Map();
frame.findAll(n => n.type === "INSTANCE").forEach(inst => {
const mc = inst.mainComponent;
const cs = mc?.parent?.type === "COMPONENT_SET" ? mc.parent : null;
const key = cs ? cs.key : mc?.key;
const name = cs ? cs.name : mc?.name;
if (key && !uniqueSets.has(key)) {
uniqueSets.set(key, { name, key, isSet: !!cs, sampleVariant: mc.name });
}
});
return [...uniqueSets.values()];
Match results against your unresolved components. Mark any newly resolved. If all components are resolved, skip 2a-iii.
2a-iii — LAST RESORT: search_design_system. Only if components remain unresolved after completing both 2a-i and 2a-ii. Search broadly — try multiple terms and synonyms (e.g., "button", "input", "nav", "card", "accordion", "header", "footer", "tag", "avatar", "toggle", "icon", etc.). Use includeComponents: true to focus on components.
Include component properties in your map — you need to know which TEXT properties each component exposes for text overrides. Create a temporary instance, read its componentProperties (and those of nested instances), then remove the temp instance.
Example component map with property info:
Component Map:
- Button → key: "abc123", type: COMPONENT_SET
Properties: { "Label#2:0": TEXT, "Has Icon#4:64": BOOLEAN }
- PricingCard → key: "ghi789", type: COMPONENT_SET
Properties: { "Device": VARIANT, "Variant": VARIANT }
Nested "Text Heading" has: { "Text#2104:5": TEXT }
Nested "Button" has: { "Label#2:0": TEXT }
Inspect existing screens first (same as components). Or use search_design_system with includeVariables: true.
WARNING: Two different variable discovery methods — do not confuse them.
use_figmawithfigma.variables.getLocalVariableCollectionsAsync()— returns only local variables defined in the current file. If this returns empty, it does not mean no variables exist. Remote/published library variables are invisible to this API.search_design_systemwithincludeVariables: true— searches across all linked libraries, including remote and published ones. This is the correct tool for discovering design system variables.Never conclude "no variables exist" based solely on
getLocalVariableCollectionsAsync()returning empty. Always also runsearch_design_systemwithincludeVariables: trueto check for library variables before deciding to create your own.
Query strategy: search_design_system matches against variable names (e.g., "Gray/gray-9", "core/gray/100", "space/400"), not categories. Run multiple short, simple queries in parallel rather than one compound query:
If initial searches return empty, try shorter fragments or different naming conventions — libraries vary widely ("grey" vs "gray", "spacing" vs "space", "color/bg" vs "background").
Inspect an existing screen's bound variables for the most authoritative results:
const frame = figma.currentPage.findOne(n => n.name === "Existing Screen");
const varMap = new Map();
frame.findAll(() => true).forEach(node => {
const bv = node.boundVariables;
if (!bv) return;
for (const [prop, binding] of Object.entries(bv)) {
const bindings = Array.isArray(binding) ? binding : [binding];
for (const b of bindings) {
if (b?.id && !varMap.has(b.id)) {
const v = await figma.variables.getVariableByIdAsync(b.id);
if (v) varMap.set(b.id, { name: v.name, id: v.id, key: v.key, type: v.resolvedType, remote: v.remote });
}
}
}
});
return [...varMap.values()];
For library variables (remote = true), import them by key with figma.variables.importVariableByKeyAsync(key). For local variables, use figma.variables.getVariableByIdAsync(id) directly.
See variable-patterns.md for binding patterns.
Search for styles using search_design_system with includeStyles: true and terms like "heading", "body", "shadow", "elevation". Or inspect what an existing screen uses:
const frame = figma.currentPage.findOne(n => n.name === "Existing Screen");
const styles = { text: new Map(), effect: new Map() };
frame.findAll(() => true).forEach(node => {
if ('textStyleId' in node && node.textStyleId) {
const s = figma.getStyleById(node.textStyleId);
if (s) styles.text.set(s.id, { name: s.name, id: s.id, key: s.key });
}
if ('effectStyleId' in node && node.effectStyleId) {
const s = figma.getStyleById(node.effectStyleId);
if (s) styles.effect.set(s.id, { name: s.name, id: s.id, key: s.key });
}
});
return {
textStyles: [...styles.text.values()],
effectStyles: [...styles.effect.values()]
};
Import library styles with figma.importStyleByKeyAsync(key), then apply with node.textStyleId = style.id or node.effectStyleId = style.id.
See text-style-patterns.md and effect-style-patterns.md for details.
Do NOT build sections as top-level page children and reparent them later — moving nodes across use_figma calls with appendChild() silently fails and produces orphaned frames. Instead, create the wrapper first, then build each section directly inside it.
Create the wrapper in its own use_figma call. Position it away from existing content and return its ID:
// Find clear space
let maxX = 0;
for (const child of figma.currentPage.children) {
maxX = Math.max(maxX, child.x + child.width);
}
const wrapper = figma.createAutoLayout("VERTICAL");
// --- Size the wrapper based on container type ---
// Full page: wrapper.resize(1440, 100); wrapper.name = "Homepage";
// Modal/dialog: wrapper.resize(640, 100); wrapper.name = "Settings Modal";
// Drawer/sidebar: wrapper.resize(360, 100); wrapper.name = "Navigation Drawer";
// Panel: wrapper.resize(400, 100); wrapper.name = "Details Panel";
// Adapt width to match the source code's actual dimensions.
wrapper.name = "VIEW_NAME";
wrapper.primaryAxisAlignItems = "CENTER";
wrapper.counterAxisAlignItems = "CENTER";
wrapper.resize(WIDTH, 100);
wrapper.layoutSizingHorizontal = "FIXED";
wrapper.x = maxX + 200;
wrapper.y = 0;
return { success: true, wrapperId: wrapper.id };
This is the most important step. Build one section at a time, each in its own use_figma call. At the start of each script, fetch the wrapper by ID and append new content directly to it.
const createdNodeIds = [];
const wrapper = await figma.getNodeByIdAsync("WRAPPER_ID_FROM_STEP_3");
// Import design system components by key
const buttonSet = await figma.importComponentSetByKeyAsync("BUTTON_SET_KEY");
const primaryButton = buttonSet.children.find(c =>
c.type === "COMPONENT" && c.name.includes("variant=primary")
) || buttonSet.defaultVariant;
// Import design system variables for colors and spacing
const bgColorVar = await figma.variables.importVariableByKeyAsync("BG_COLOR_VAR_KEY");
const spacingVar = await figma.variables.importVariableByKeyAsync("SPACING_VAR_KEY");
// Build section frame with variable bindings (not hardcoded values)
const section = figma.createAutoLayout();
section.name = "Header";
section.setBoundVariable("paddingLeft", spacingVar);
section.setBoundVariable("paddingRight", spacingVar);
const bgPaint = figma.variables.setBoundVariableForPaint(
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', bgColorVar
);
section.fills = [bgPaint];
// Import and apply text/effect styles
const shadowStyle = await figma.importStyleByKeyAsync("SHADOW_STYLE_KEY");
section.effectStyleId = shadowStyle.id;
// Create component instances inside the section
const btnInstance = primaryButton.createInstance();
section.appendChild(btnInstance);
createdNodeIds.push(btnInstance.id);
// Append section to wrapper
wrapper.appendChild(section);
section.layoutSizingHorizontal = "FILL"; // AFTER appending
createdNodeIds.push(section.id);
return { success: true, createdNodeIds };
After each section, validate with get_screenshot before moving on. Look closely for cropped/clipped text (line heights cutting off content) and overlapping elements — these are the most common issues and easy to miss at a glance.
Component instances ship with placeholder text ("Title", "Heading", "Button"). Use the component property keys you discovered in Step 2 to override them with setProperties() — this is more reliable than direct node.characters manipulation. See component-patterns.md for the full pattern.
For nested instances that expose their own TEXT properties, call setProperties() on the nested instance:
const nestedHeading = cardInstance.findOne(n => n.type === "INSTANCE" && n.name === "Text Heading");
if (nestedHeading) {
nestedHeading.setProperties({ "Text#2104:5": "Actual heading from source code" });
}
Only fall back to direct node.characters for text that is NOT managed by any component property.
When translating code components to Figma instances, check the component's default prop values in the source code, not just what's explicitly passed. For example, <Button size="small">Register</Button> with no variant prop — check the component definition to find variant = "primary" as the default. Selecting the wrong variant (e.g., Neutral instead of Primary) produces a visually incorrect result that's easy to miss.
| Build manually | Import from design system |
|---|---|
| Wrapper frame | Components: buttons, cards, inputs, nav, etc. |
| Section container frames | Variables: colors (fills, strokes), spacing (padding, gap), radii |
| Layout grids (rows, columns) | Text styles: heading, body, caption, etc. |
| Effect styles: shadows, blurs, etc. |
Never hardcode hex colors or pixel spacing when a design system variable exists. Use setBoundVariable for spacing/radii and setBoundVariableForPaint for colors. Apply text styles with node.textStyleId and effect styles with node.effectStyleId.
After composing all sections, call get_screenshot on the wrapper frame and compare against the source. Fix any issues with targeted use_figma calls — don't rebuild the entire view.
Screenshot individual sections, not just the full view. A full-view screenshot at reduced resolution hides text truncation, wrong colors, and placeholder text that hasn't been overridden. Take a screenshot of each section by node ID to catch:
generate_figma_design capture (see below)If you ran generate_figma_design in parallel (mandatory when the source contains images), transfer the captured images into your design system output:
type === "IMAGE":
const capture = await figma.getNodeByIdAsync("CAPTURE_NODE_ID");
const imageNodes = [];
capture.findAll(n => {
if (n.fills && Array.isArray(n.fills)) {
for (const fill of n.fills) {
if (fill.type === "IMAGE") {
imageNodes.push({ name: n.name, id: n.id, imageHash: fill.imageHash });
return true;
}
}
}
return false;
});
return imageNodes;
targetFrame.fills = [{ type: "IMAGE", imageHash: "hash_from_capture", scaleMode: "FILL" }];
generate_figma_design capture output after all images are transferred.When updating rather than creating from scratch:
get_metadata to inspect the existing screen structure.get_screenshot after each modification.// Example: Swap a button variant in an existing screen
const existingButton = await figma.getNodeByIdAsync("EXISTING_BUTTON_INSTANCE_ID");
if (existingButton && existingButton.type === "INSTANCE") {
// Import the updated component
const buttonSet = await figma.importComponentSetByKeyAsync("BUTTON_SET_KEY");
const newVariant = buttonSet.children.find(c =>
c.name.includes("variant=primary") && c.name.includes("size=lg")
) || buttonSet.defaultVariant;
existingButton.swapComponent(newVariant);
}
return { success: true, mutatedNodeIds: [existingButton.id] };
For detailed API patterns and gotchas, load these from the figma-use references as needed:
Follow the error recovery process from figma-use:
get_metadata or get_screenshot to inspect the current file state.Because this skill works incrementally (one section per call), errors are naturally scoped to a single section. Previous sections from successful calls remain intact.
use_figma call.get_screenshot to catch issues early.