# Common Patterns

> Part of the [use_figma skill](../SKILL.md). 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

```js
(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

```js
(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

```js
(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

```js
(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

```js
(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

```js
(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.

```js
(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.

```js
(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)

```js
(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**

```js
(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**

```js
(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

```js
(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())
  }
})()
```
