All skills
openai avatar

/figma-use

@0e7823c official
by openaiopenai/skills28k stars
1,891

**MANDATORY prerequisite** — you MUST invoke this skill BEFORE every `use_figma` tool call. NEVER call `use_figma` directly without loading this skill first. Skipping it causes common, hard-to-debug failures. Trigger whenever the user wants to perform a write action or a unique read action that requires JavaScript execution in the Figma file context — e.g. create/edit/delete nodes, set up variables or tokens, build components and variants, modify auto-layout or fills, bind variables to properties, or inspect file structure programmatically.

Use this Skill: https://skilld.dev/gh/openai/skills/figma-use

This session only. Nothing lands on disk.

referencescommon-patterns.md

≈4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Common Patterns

Part of the use_figma skill. Working code examples for frequently used operations.

Contents

  • Basic Script Structure
  • Create a Styled Shape
  • Create a Text Node
  • Create Frame with Auto-Layout
  • Create Variable Collections and Bindings
  • Create Components and Import by Key
  • Component Sets with Variable Modes
  • Multi-Step Large ComponentSet Pattern
  • Read Existing Nodes and Return Data

Basic Script Structure

(async () => {
  try {
    const createdNodeIds = []
    const mutatedNodeIds = []

    // Your code here — track every node you create or mutate
    // createdNodeIds.push(newNode.id)
    // mutatedNodeIds.push(existingNode.id)

    figma.closePlugin(JSON.stringify({
      success: true,
      createdNodeIds,
      mutatedNodeIds,
      // Plus any other useful data for subsequent calls
      count: createdNodeIds.length
    }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Create a Styled Shape

(async () => {
  try {
    // Find clear space to the right of existing content
    const page = figma.currentPage
    let maxX = 0
    for (const child of page.children) {
      maxX = Math.max(maxX, child.x + child.width)
    }

    const rect = figma.createRectangle()
    rect.name = "Blue Box"
    rect.resize(200, 100)
    rect.fills = [{ type: 'SOLID', color: { r: 0.047, g: 0.549, b: 0.914 } }]
    rect.cornerRadius = 8
    rect.x = maxX + 100  // offset from existing content
    rect.y = 0
    figma.currentPage.appendChild(rect)
    figma.closePlugin(JSON.stringify({ nodeId: rect.id }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Create a Text Node

(async () => {
  try {
    // Find clear space to the right of existing content
    const page = figma.currentPage
    let maxX = 0
    for (const child of page.children) {
      maxX = Math.max(maxX, child.x + child.width)
    }

    await figma.loadFontAsync({ family: "Inter", style: "Regular" })
    const text = figma.createText()
    text.characters = "Hello World"
    text.fontSize = 16
    text.fills = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }]
    text.textAutoResize = 'WIDTH_AND_HEIGHT'
    text.x = maxX + 100
    text.y = 0
    figma.currentPage.appendChild(text)
    figma.closePlugin(JSON.stringify({ nodeId: text.id }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Create Frame with Auto-Layout

(async () => {
  try {
    // Find clear space to the right of existing content
    const page = figma.currentPage
    let maxX = 0
    for (const child of page.children) {
      maxX = Math.max(maxX, child.x + child.width)
    }

    const frame = figma.createFrame()
    frame.name = "Card"
    frame.layoutMode = 'VERTICAL'
    frame.primaryAxisAlignItems = 'MIN'
    frame.counterAxisAlignItems = 'MIN'
    frame.paddingLeft = 16
    frame.paddingRight = 16
    frame.paddingTop = 12
    frame.paddingBottom = 12
    frame.itemSpacing = 8
    frame.layoutSizingHorizontal = 'HUG'
    frame.layoutSizingVertical = 'HUG'
    frame.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
    frame.cornerRadius = 8
    frame.x = maxX + 100
    frame.y = 0
    figma.currentPage.appendChild(frame)
    figma.closePlugin(JSON.stringify({ nodeId: frame.id }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Create Variable Collection with Multiple Modes

(async () => {
  try {
    const collection = figma.variables.createVariableCollection("Theme/Colors")
    // Rename the default mode
    collection.renameMode(collection.modes[0].modeId, "Light")
    const darkModeId = collection.addMode("Dark")
    const lightModeId = collection.modes[0].modeId

    const bgVar = figma.variables.createVariable("bg", collection, "COLOR")
    bgVar.setValueForMode(lightModeId, { r: 1, g: 1, b: 1, a: 1 })
    bgVar.setValueForMode(darkModeId, { r: 0.1, g: 0.1, b: 0.1, a: 1 })

    const textVar = figma.variables.createVariable("text", collection, "COLOR")
    textVar.setValueForMode(lightModeId, { r: 0, g: 0, b: 0, a: 1 })
    textVar.setValueForMode(darkModeId, { r: 1, g: 1, b: 1, a: 1 })

    figma.closePlugin(JSON.stringify({
      collectionId: collection.id,
      lightModeId,
      darkModeId,
      bgVarId: bgVar.id,
      textVarId: textVar.id
    }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Bind Color Variable to a Fill

(async () => {
  try {
    const variable = figma.variables.getVariableById("VariableID:1:2")
    const rect = figma.createRectangle()
    const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }

    // setBoundVariableForPaint returns a NEW paint — capture it!
    const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", variable)
    rect.fills = [boundPaint]

    figma.closePlugin(JSON.stringify({ nodeId: rect.id }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Create Component Variants with Component Properties

Component properties (TEXT, BOOLEAN, INSTANCE_SWAP) MUST be added inside the per-variant loop, BEFORE combineAsVariants. The component set inherits them from its children.

(async () => {
  try {
    await figma.loadFontAsync({ family: "Inter", style: "Regular" })

    // Assume defaultIconComp is an existing icon component (discovered earlier)
    const defaultIconComp = figma.getNodeById('ICON_COMPONENT_ID')

    const components = []
    const variants = ["primary", "secondary"]

    for (const variant of variants) {
      const comp = figma.createComponent()
      comp.name = `variant=${variant}`
      comp.layoutMode = 'HORIZONTAL'
      comp.primaryAxisAlignItems = 'CENTER'
      comp.counterAxisAlignItems = 'CENTER'
      comp.paddingLeft = 12
      comp.paddingRight = 12
      comp.paddingTop = 8
      comp.paddingBottom = 8
      comp.layoutSizingHorizontal = 'HUG'
      comp.layoutSizingVertical = 'HUG'
      comp.cornerRadius = 6
      comp.itemSpacing = 8

      // TEXT property — label
      const labelKey = comp.addComponentProperty('Label', 'TEXT', 'Button')
      const label = figma.createText()
      label.characters = "Button"
      label.fontSize = 14
      comp.appendChild(label)
      label.componentPropertyReferences = { characters: labelKey }

      // BOOLEAN + INSTANCE_SWAP — icon slot
      const showIconKey = comp.addComponentProperty('Show Icon', 'BOOLEAN', false)
      const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', defaultIconComp.id)
      const iconInstance = defaultIconComp.createInstance()
      comp.insertChild(0, iconInstance)  // icon before label
      iconInstance.componentPropertyReferences = {
        visible: showIconKey,
        mainComponent: iconSlotKey
      }

      components.push(comp)
    }

    const componentSet = figma.combineAsVariants(components, figma.currentPage)
    componentSet.name = "Button"

    // Layout variants in a row after combining (they stack at 0,0 by default)
    const colW = 140
    componentSet.children.forEach((child, i) => {
      child.x = i * colW
      child.y = 0
    })
    // Resize from actual child bounds — formula-based sizing is error-prone
    let maxX = 0, maxY = 0
    for (const c of componentSet.children) {
      maxX = Math.max(maxX, c.x + c.width)
      maxY = Math.max(maxY, c.y + c.height)
    }
    componentSet.resizeWithoutConstraints(maxX + 40, maxY + 40)

    figma.closePlugin(JSON.stringify({
      componentSetId: componentSet.id,
      componentIds: components.map(c => c.id)
    }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Import a Component by Key (Team Libraries)

importComponentByKeyAsync and importComponentSetByKeyAsync import components from team libraries (not the same file you're working in). For components in the current file, use figma.getNodeByIdAsync() or findOne()/findAll() to locate them directly.

(async () => {
  try {
    // Import a single published component by key
    const comp = await figma.importComponentByKeyAsync("COMPONENT_KEY")
    const instance = comp.createInstance()
    instance.x = 40
    instance.y = 40
    figma.currentPage.appendChild(instance)

    // Import a published component set by key and select a variant
    const compSet = await figma.importComponentSetByKeyAsync("COMPONENT_SET_KEY")
    const variant =
      compSet.children.find((c) =>
        c.type === "COMPONENT" && c.name.includes("size=md")
      ) || compSet.defaultVariant

    const variantInstance = variant.createInstance()
    variantInstance.x = 240
    variantInstance.y = 40
    figma.currentPage.appendChild(variantInstance)

    figma.closePlugin(JSON.stringify({
      componentId: comp.id,
      componentSetId: compSet.id,
      placedInstanceIds: [instance.id, variantInstance.id]
    }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Component Set with Variable Modes (Full Pattern)

(async () => {
  try {
    await figma.loadFontAsync({ family: "Inter", style: "Medium" })

    // 1. Create color collection with modes per variant
    const colors = figma.variables.createVariableCollection("Component/Colors")
    colors.renameMode(colors.modes[0].modeId, "primary")
    const primaryMode = colors.modes[0].modeId
    const secondaryMode = colors.addMode("secondary")

    const bgVar = figma.variables.createVariable("bg", colors, "COLOR")
    bgVar.setValueForMode(primaryMode, { r: 0, g: 0.4, b: 0.9, a: 1 })
    bgVar.setValueForMode(secondaryMode, { r: 0, g: 0, b: 0, a: 0 })

    const textVar = figma.variables.createVariable("text-color", colors, "COLOR")
    textVar.setValueForMode(primaryMode, { r: 1, g: 1, b: 1, a: 1 })
    textVar.setValueForMode(secondaryMode, { r: 0.1, g: 0.1, b: 0.1, a: 1 })

    // 2. Create components with variable bindings
    const modeMap = { primary: primaryMode, secondary: secondaryMode }
    const components = []

    for (const [variantName, modeId] of Object.entries(modeMap)) {
      const comp = figma.createComponent()
      comp.name = "variant=" + variantName
      comp.layoutMode = "HORIZONTAL"
      comp.primaryAxisAlignItems = "CENTER"
      comp.counterAxisAlignItems = "CENTER"
      comp.paddingLeft = 12; comp.paddingRight = 12
      comp.layoutSizingHorizontal = "HUG"
      comp.layoutSizingVertical = "HUG"
      comp.cornerRadius = 6

      // Bind background fill to variable
      const bgPaint = figma.variables.setBoundVariableForPaint(
        { type: "SOLID", color: { r: 0, g: 0, b: 0 } }, "color", bgVar
      )
      comp.fills = [bgPaint]

      // Add text with bound color
      const label = figma.createText()
      label.fontName = { family: "Inter", style: "Medium" }
      label.characters = "Button"
      label.fontSize = 14
      const textPaint = figma.variables.setBoundVariableForPaint(
        { type: "SOLID", color: { r: 0, g: 0, b: 0 } }, "color", textVar
      )
      label.fills = [textPaint]
      comp.appendChild(label)

      // 3. CRITICAL: Set explicit mode so this variant renders correctly
      comp.setExplicitVariableModeForCollection(colors.id, modeId)

      components.push(comp)
    }

    // 4. Combine into component set
    const componentSet = figma.combineAsVariants(components, figma.currentPage)
    componentSet.name = "Button"

    figma.closePlugin(JSON.stringify({
      componentSetId: componentSet.id,
      colorCollectionId: colors.id
    }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Large ComponentSet with Variable Modes (Multi-Step Pattern)

For component sets with many variants (50+), split into multiple use_figma calls:

Call 1: Create variable collections and return IDs

(async () => {
  try {
    // Hex-to-0-1 helper
    const hex = (h) => {
      if (!h) return { r: 0, g: 0, b: 0, a: 0 }; // transparent
      return {
        r: parseInt(h.slice(1,3), 16) / 255,
        g: parseInt(h.slice(3,5), 16) / 255,
        b: parseInt(h.slice(5,7), 16) / 255,
        a: 1
      };
    };

    const coll = figma.variables.createVariableCollection("MyComponent/Colors");
    coll.renameMode(coll.modes[0].modeId, "mode1");
    const mode2Id = coll.addMode("mode2");

    // Create variables from data map
    const colorData = { "bg/default": ["#0B6BCB", "#636B74"], /* ... */ };
    const modeOrder = ["mode1", "mode2"];
    const modeIds = { mode1: coll.modes[0].modeId, mode2: mode2Id };
    const varIds = {};

    for (const [name, values] of Object.entries(colorData)) {
      const v = figma.variables.createVariable(name, coll, "COLOR");
      values.forEach((hex_val, i) => {
        v.setValueForMode(modeIds[modeOrder[i]], hex_val ? hex(hex_val) : { r:0, g:0, b:0, a:0 });
      });
      varIds[name] = v.id;
    }

    // Return ALL IDs — needed by subsequent calls
    figma.closePlugin(JSON.stringify({ collId: coll.id, modeIds, varIds }));
  } catch (e) {
    figma.closePluginWithFailure(e.toString());
  }
})()

Call 2: Create components using stored IDs, combine and layout

(async () => {
  try {
    await figma.loadFontAsync({ family: "Inter", style: "Semi Bold" });

    // Paste IDs from Call 1 as literals
    const collId = "VariableCollectionId:X:Y";
    const modeIds = { mode1: "X:0", mode2: "X:1" };
    const varIds = { /* ... from Call 1 ... */ };

    const getVar = (id) => figma.variables.getVariableById(id);
    const bindColor = (varId) => figma.variables.setBoundVariableForPaint(
      { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', getVar(varId)
    );

    const components = [];
    for (const mode of ["mode1", "mode2"]) {
      for (const state of ["default", "hover"]) {
        const comp = figma.createComponent();
        comp.name = `mode=${mode}, state=${state}`;
        comp.layoutMode = 'HORIZONTAL';
        comp.primaryAxisAlignItems = 'CENTER';
        comp.counterAxisAlignItems = 'CENTER';
        comp.layoutSizingHorizontal = 'HUG';
        comp.layoutSizingVertical = 'HUG';
        comp.fills = [bindColor(varIds[`bg/${state}`])];
        comp.setExplicitVariableModeForCollection(collId, modeIds[mode]);
        // ... add text children ...
        components.push(comp);
      }
    }

    // Combine — all children stack at (0,0)!
    const cs = figma.combineAsVariants(components, figma.currentPage);
    cs.name = "MyComponent";

    // CRITICAL: layout variants in a structured grid mapped to variant axes.
    const stateOrder = ["default", "hover"];
    const modeOrder2 = ["mode1", "mode2"];
    const colW = 140, rowH = 56;

    for (const child of cs.children) {
      const props = Object.fromEntries(
        child.name.split(', ').map(p => p.split('='))
      );
      const col = stateOrder.indexOf(props.state);
      const row = modeOrder2.indexOf(props.mode);
      child.x = col * colW;
      child.y = row * rowH;
    }
    // Resize from actual child bounds
    let maxX = 0, maxY = 0;
    for (const child of cs.children) {
      maxX = Math.max(maxX, child.x + child.width);
      maxY = Math.max(maxY, child.y + child.height);
    }
    cs.resizeWithoutConstraints(maxX + 40, maxY + 40);

    // Wrap in section
    const section = figma.createSection();
    section.name = "MyComponent Section";
    section.appendChild(cs);
    section.resizeWithoutConstraints(cs.width + 200, cs.height + 200);

    figma.closePlugin(JSON.stringify({ csId: cs.id, count: components.length }));
  } catch (e) {
    figma.closePluginWithFailure(e.toString());
  }
})()

Read Existing Nodes and Return Data

(async () => {
  try {
    const page = figma.currentPage
    const nodes = page.findAll(n => n.type === 'FRAME')
    const data = nodes.map(n => ({
      id: n.id,
      name: n.name,
      width: n.width,
      height: n.height,
      childCount: n.children?.length || 0
    }))
    figma.closePlugin(JSON.stringify({ frames: data }))
  } catch (e) {
    figma.closePluginWithFailure(e.toString())
  }
})()

Source: SKILL.md on GitHub

No alerts17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill provides a robust framework for interacting with the Figma Plugin API via a Model Context Protocol (MCP) server. It includes comprehensive documentation, design system alignment guidelines, and architectural best practices. While the skill enables dynamic code execution and processes external document data, these are standard features of its intended use as a development tool for the Figma platform. The instructions emphasize validation and incremental workflows to manage these capabilities safely.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 0e7823c. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Activeupdated 6 months ago
  • figma
  • plugin-api
  • javascript
  • design-automation
  • variables
  • components
  • design-systems

README badge

README badge for openai/skills/figma-use

This skill loads the Figma Plugin API ruleset and reference docs, making it a prerequisite for executing JavaScript in Figma files via the use_figma MCP tool. It covers critical constraints like async/await patterns, page switching, node ID tracking, and incremental workflow practices to avoid race conditions and atomicity failures.

Generated from the current SKILL.md.

Do I need to load this skill before every use_figma call?
Yes. You must pass `skillNames: "figma-use"` and load this skill before every `use_figma` tool call. Skipping it causes hard-to-debug failures.
Can I use figma.notify() to show messages in the plugin UI?
No. `figma.notify()` throws "not implemented". Use `return` to send data back to the agent instead.
What should I do if a use_figma call fails with an error?
Stop and read the error message carefully. `use_figma` is atomic — if a script errors, no changes are made to the file. Fix the script based on the error and retry.
How do I switch to a different page in a use_figma script?
Use `await figma.setCurrentPageAsync(page)` to switch pages and load their content. The sync setter `figma.currentPage = page` throws an error.
What should I return from a use_figma script that creates or modifies nodes?
Always return all affected node IDs in a structured object, e.g. `return { createdNodeIds: [...], mutatedNodeIds: [...] }`. This is required so subsequent calls can reference those nodes.

Generated from the current SKILL.md. These answers refresh after source changes.