Build / Update Screens from Design System
Use this skill to create or update full-page screens in Figma by **reusing the published design system** — components, variables, and styles — rather than drawing primitives with hardcoded values. 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](../figma-use/SKILL.md) 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.
Skill Boundaries
- Use this skill when the deliverable is a **Figma screen** (new or updated) composed of design system component instances.
- If the user wants to generate **code from a Figma design**, switch to [figma-implement-design](../figma-implement-design/SKILL.md).
- If the user wants to create **new reusable components or variants**, use [figma-use](../figma-use/SKILL.md) directly.
- If the user wants to write **Code Connect mappings**, switch to [figma-code-connect-components](../figma-code-connect-components/SKILL.md).
Prerequisites
- Figma MCP server must be connected
- The target Figma file must have a published design system with components (or access to a team library)
- User should provide either:
- A Figma file URL / file key to work in
- Or context about which file to target (the agent can discover pages)
- Source code or description of the screen to build/update
Parallel Workflow with generate_figma_design (Web Apps Only)
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:
- **In parallel:**
- Start building the screen using this skill's workflow (use_figma + design system components)
- Run `generate_figma_design` to capture a pixel-perfect screenshot of the running web app
- **Once both complete:** Update the use_figma output to match the pixel-perfect layout from the `generate_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.
- **Once confirmed looking good:** Delete the `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 workflow only applies to web apps** where `generate_figma_design` can capture the running page. For non-web apps (iOS, Android, etc.) or when updating existing screens, use the standard workflow below.
Required Workflow
**Follow these steps in order. Do not skip steps.**
Step 1: Understand the Screen
Before touching Figma, understand what you're building:
- If building from code, read the relevant source files to understand the page structure, sections, and which components are used.
- Identify the major sections of the screen (e.g., Header, Hero, Content Panels, Pricing Grid, FAQ Accordion, Footer).
- For each section, list the UI components involved (buttons, inputs, cards, navigation pills, accordions, etc.).
Step 2: Discover Design System — Components, Variables, and Styles
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: Discover components
**Preferred: inspect existing screens first.** If the target file already contains screens using the same design system, skip `search_design_system` and inspect existing instances directly. 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()];Only fall back to `search_design_system` when the file has no existing screens to reference. When using it, **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 }#### 2b: Discover variables (colors, spacing, radii)
**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_figma` with `figma.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_system` with `includeVariables: 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 run `search_design_system` with `includeVariables: true` to 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:
- **Primitive colors:** "gray", "red", "blue", "green", "white", "brand"
- **Semantic colors:** "background", "foreground", "border", "surface", "text"
- **Spacing/sizing:** "space", "radius", "gap", "padding"
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](../figma-use/references/variable-patterns.md) for binding patterns.
#### 2c: Discover styles (text styles, effect styles)
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](../figma-use/references/text-style-patterns.md) and [effect-style-patterns.md](../figma-use/references/effect-style-patterns.md) for details.
Step 3: Create the Page Wrapper Frame First
**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 page 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.createFrame();
wrapper.name = "Homepage";
wrapper.layoutMode = "VERTICAL";
wrapper.primaryAxisAlignItems = "CENTER";
wrapper.counterAxisAlignItems = "CENTER";
wrapper.resize(1440, 100);
wrapper.layoutSizingHorizontal = "FIXED";
wrapper.layoutSizingVertical = "HUG";
wrapper.x = maxX + 200;
wrapper.y = 0;
return { success: true, wrapperId: wrapper.id };Step 4: Build Each Section Inside the Wrapper
**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.createFrame();
section.name = "Header";
section.layoutMode = "HORIZONTAL";
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.
#### Override instance text with setProperties()
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](../figma-use/references/component-patterns.md#overriding-text-in-a-component-instance) 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.
#### Read source code defaults carefully
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.
#### What to build manually vs. import from design system
| Build manually | Import from design system | |----------------|--------------------------| | Page 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`.
Step 5: Validate the Full Screen
After composing all sections, call `get_screenshot` on the full page frame and compare against the source. Fix any issues with targeted `use_figma` calls — don't rebuild the entire screen.
**Screenshot individual sections, not just the full page.** A full-page 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:
- **Cropped/clipped text** — line heights or frame sizing cutting off descenders, ascenders, or entire lines
- **Overlapping content** — elements stacking on top of each other due to incorrect sizing or missing auto-layout
- Placeholder text still showing ("Title", "Heading", "Button")
- Truncated content from layout sizing bugs
- Wrong component variants (e.g., Neutral vs Primary button)
Step 6: Updating an Existing Screen
When updating rather than creating from scratch:
- Use `get_metadata` to inspect the existing screen structure.
- Identify which sections need updating and which can stay.
- For each section that needs changes:
- Locate the existing nodes by ID or name
- Swap component instances if the design system component changed
- Update text content, variant properties, or layout as needed
- Remove deprecated sections
- Add new sections
- Validate with `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] };Reference Docs
For detailed API patterns and gotchas, load these from the [figma-use](../figma-use/SKILL.md) references as needed:
- [component-patterns.md](../figma-use/references/component-patterns.md) — importing by key, finding variants, setProperties, text overrides, working with instances
- [variable-patterns.md](../figma-use/references/variable-patterns.md) — creating/binding variables, importing library variables, scopes, aliasing, discovering existing variables
- [text-style-patterns.md](../figma-use/references/text-style-patterns.md) — creating/applying text styles, importing library text styles, type ramps
- [effect-style-patterns.md](../figma-use/references/effect-style-patterns.md) — creating/applying effect styles (shadows), importing library effect styles
- [gotchas.md](../figma-use/references/gotchas.md) — layout pitfalls (HUG/FILL interactions, counterAxisAlignItems, sizing order), paint/color issues, page context resets
Error Recovery
Follow the error recovery process from [figma-use](../figma-use/SKILL.md#6-error-recovery--self-correction):
- **STOP** on error — do not retry immediately.
- **Read the error message carefully** to understand what went wrong.
- If the error is unclear, call `get_metadata` or `get_screenshot` to inspect the current file state.
- **Fix the script** based on the error message.
- **Retry** the corrected script — this is safe because failed scripts are atomic (nothing is created if a script errors).
Because this skill works incrementally (one section per call), errors are naturally scoped to a single section. Previous sections from successful calls remain intact.
Best Practices
- **Always search before building.** The design system likely has the component, variable, or style you need. Manual construction and hardcoded values should be the exception, not the rule.
- **Search broadly.** Try synonyms and partial terms. A "NavigationPill" might be found under "pill", "nav", "tab", or "chip". For variables, search "color", "spacing", "radius", etc.
- **Prefer design system tokens over hardcoded values.** Use variable bindings for colors, spacing, and radii. Use text styles for typography. Use effect styles for shadows. This keeps the screen linked to the design system.
- **Prefer component instances over manual builds.** Instances stay linked to the source component and update automatically when the design system evolves.
- **Work section by section.** Never build more than one major section per `use_figma` call.
- **Return node IDs from every call.** You'll need them to compose sections and for error recovery.
- **Validate visually after each section.** Use `get_screenshot` to catch issues early.
- **Match existing conventions.** If the file already has screens, match their naming, sizing, and layout patterns.