ChatGPT Codex CLI OpenAI Skill

figma-use

**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 r

LLM Mart · 0 points · 28 views 1 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download openai-skills-skills_.curated_figma-use-49f948f.zip · 168 KB
Part of openai/skills — 44 skills

Install

skills CLI npx skills add https://github.com/openai/skills/tree/main/skills/.curated/figma-use
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install openai-skills@llmmart
Git git clone https://github.com/openai/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole openai/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

use_figma — Figma Plugin API Skill

Use use_figma MCP to execute JavaScript in Figma files via the Plugin API. All detailed reference docs live in references/.

Always pass skillNames: "figma-use" when calling use_figma. This is a logging parameter used to track skill usage — it does not affect execution.

If the task involves building or updating a full page, screen, or multi-section layout in Figma from code, also load figma-generate-design. It provides the workflow for discovering design system components via search_design_system, importing them, and assembling screens incrementally. Both skills work together: this one for the API rules, that one for the screen-building workflow.

Before anything, load plugin-api-standalone.index.md to understand what is possible. When you are asked to write plugin API code, use this context to grep plugin-api-standalone.d.ts for relevant types, methods, and properties. This is the definitive source of truth for the API surface. It is a large typings file, so do not load it all at once, grep for relevant sections as needed.

IMPORTANT: Whenever you work with design systems, start with working-with-design-systems/wwds.md to understand the key concepts, processes, and guidelines for working with design systems in Figma. Then load the more specific references for components, variables, text styles, and effect styles as needed.

1. Critical Rules

  1. Use return to send data back. The return value is JSON-serialized automatically (objects, arrays, strings, numbers). Do NOT call figma.closePlugin() or wrap code in an async IIFE — this is handled for you.
  2. Write plain JavaScript with top-level await and return. Code is automatically wrapped in an async context. Do NOT wrap in (async () => { ... })().
  3. figma.notify() throws "not implemented" — never use it 3a. getPluginData() / setPluginData() are not supported in use_figma — do not use them. Use getSharedPluginData() / setSharedPluginData() instead (these ARE supported), or track node IDs by returning them and passing them to subsequent calls.
  4. console.log() is NOT returned — use return for output
  5. Work incrementally in small steps. Break large operations into multiple use_figma calls. Validate after each step. This is the single most important practice for avoiding bugs.
  6. Colors are 0–1 range (not 0–255): {r: 1, g: 0, b: 0} = red
  7. Fills/strokes are read-only arrays — clone, modify, reassign
  8. Font MUST be loaded before any text operation: await figma.loadFontAsync({family, style})
  9. Pages load incrementally — use await figma.setCurrentPageAsync(page) to switch pages and load their content (see Page Rules below)
  10. setBoundVariableForPaint returns a NEW paint — must capture and reassign
  11. createVariable accepts collection object or ID string (object preferred)
  12. layoutSizingHorizontal/Vertical = 'FILL' MUST be set AFTER parent.appendChild(child) — setting before append throws. Same applies to 'HUG' on non-auto-layout nodes.
  13. Position new top-level nodes away from (0,0). Nodes appended directly to the page default to (0,0). Scan figma.currentPage.children to find a clear position (e.g., to the right of the rightmost node). This only applies to page-level nodes — nodes nested inside other frames or auto-layout containers are positioned by their parent. See Gotchas.
  14. On use_figma error, STOP. Do NOT immediately retry. Failed scripts are atomic — if a script errors, it is not executed at all and no changes are made to the file. Read the error message carefully, fix the script, then retry. See Error Recovery.
  15. MUST return ALL created/mutated node IDs. Whenever a script creates new nodes or mutates existing ones on the canvas, collect every affected node ID and return them in a structured object (e.g. return { createdNodeIds: [...], mutatedNodeIds: [...] }). This is essential for subsequent calls to reference, validate, or clean up those nodes.
  16. Always set variable.scopes explicitly when creating variables. The default ALL_SCOPES pollutes every property picker — almost never what you want. Use specific scopes like ["FRAME_FILL", "SHAPE_FILL"] for backgrounds, ["TEXT_FILL"] for text colors, ["GAP"] for spacing, etc. See variable-patterns.md for the full list.
  17. await every Promise. Never leave a Promise unawaited — unawaited async calls (e.g. figma.loadFontAsync(...) without await, or figma.setCurrentPageAsync(page) without await) will fire-and-forget, causing silent failures or race conditions. The script may return before the async operation completes, leading to missing data or half-applied changes.

For detailed WRONG/CORRECT examples of each rule, see Gotchas & Common Mistakes.

2. Page Rules (Critical)

Page context resets between use_figma callsfigma.currentPage starts on the first page each time.

Switching pages

Use await figma.setCurrentPageAsync(page) to switch pages and load their content. The sync setter figma.currentPage = page throws an error in use_figma runtimes.

// Switch to a specific page (loads its content)
const targetPage = figma.root.children.find((p) => p.name === "My Page");
await figma.setCurrentPageAsync(targetPage);
// targetPage.children is now populated

// Iterate over all pages
for (const page of figma.root.children) {
  await figma.setCurrentPageAsync(page);
  // page.children is now loaded — read or modify them here
}

Across script runs

figma.currentPage resets to the first page at the start of each use_figma call. If your workflow spans multiple calls and targets a non-default page, call await figma.setCurrentPageAsync(page) at the start of each invocation.

You can call use_figma multiple times to incrementally build on the file state, or to retrieve information before writing another script. For example, write a script to get metadata about existing nodes, return that data, then use it in a subsequent script to modify those nodes.

3. return Is Your Output Channel

The agent sees ONLY the value you return. Everything else is invisible.

  • Returning IDs (CRITICAL): Every script that creates or mutates canvas nodes MUST return all affected node IDs — e.g. return { createdNodeIds: [...], mutatedNodeIds: [...] }. This is a hard requirement, not optional.
  • Progress reporting: return { createdNodeIds: [...], count: 5, errors: [] }
  • Error info: Thrown errors are automatically captured and returned — just let them propagate or throw explicitly.
  • console.log() output is never returned to the agent
  • Always return actionable data (IDs, counts, status) so subsequent calls can reference created objects

4. Editor Mode

use_figma works in design mode (editorType "figma", the default). FigJam ("figjam") has a different set of available node types — most design nodes are blocked there.

Available in design mode: Rectangle, Frame, Component, Text, Ellipse, Star, Line, Vector, Polygon, BooleanOperation, Slice, Page, Section, TextPath.

Blocked in design mode: Sticky, Connector, ShapeWithText, CodeBlock, Slide, SlideRow, Webpage.

5. Incremental Workflow (How to Avoid Bugs)

The most common cause of bugs is trying to do too much in a single use_figma call. Work in small steps and validate after each one.

The pattern

  1. Inspect first. Before creating anything, run a read-only use_figma to discover what already exists in the file — pages, components, variables, naming conventions. Match what's there.
  2. Do one thing per call. Create variables in one call, create components in the next, compose layouts in another. Don't try to build an entire screen in one script.
  3. Return IDs from every call. Always return created node IDs, variable IDs, collection IDs as objects (e.g. return { createdNodeIds: [...] }). You'll need these as inputs to subsequent calls.
  4. Validate after each step. Use get_metadata to verify structure (counts, names, hierarchy, positions). Use get_screenshot after major milestones to catch visual issues.
  5. Fix before moving on. If validation reveals a problem, fix it before proceeding to the next step. Don't build on a broken foundation.

Suggested step order for complex tasks

Step 1: Inspect file — discover existing pages, components, variables, conventions
Step 2: Create tokens/variables (if needed)
       → validate with get_metadata
Step 3: Create individual components
       → validate with get_metadata + get_screenshot
Step 4: Compose layouts from component instances
       → validate with get_screenshot
Step 5: Final verification

What to validate at each step

After... Check with get_metadata Check with get_screenshot
Creating variables Collection count, variable count, mode names
Creating components Child count, variant names, property definitions Variants visible, not collapsed, grid readable
Binding variables Node properties reflect bindings Colors/tokens resolved correctly
Composing layouts Instance nodes have mainComponent, hierarchy correct No cropped/clipped text, no overlapping elements, correct spacing

6. Error Recovery & Self-Correction

use_figma is atomic — failed scripts do not execute. If a script errors, no changes are made to the file. The file remains in the same state as before the call. This means there are no partial nodes, no orphaned elements from the failed script, and retrying after a fix is safe.

When use_figma returns an error

  1. STOP. Do not immediately fix the code and retry.
  2. Read the error message carefully. Understand exactly what went wrong — wrong API usage, missing font, invalid property value, etc.
  3. If the error is unclear, call get_metadata or get_screenshot to understand the current file state.
  4. Fix the script based on the error message.
  5. Retry the corrected script.

Common self-correction patterns

Error message Likely cause How to fix
"not implemented" Used figma.notify() Remove it — use return for output
"node must be an auto-layout frame..." Set FILL/HUG before appending to auto-layout parent Move appendChild before layoutSizingX = 'FILL'
"Setting figma.currentPage is not supported" Used sync page setter Use await figma.setCurrentPageAsync(page)
Property value out of range Color channel > 1 (used 0–255 instead of 0–1) Divide by 255
"Cannot read properties of null" Node doesn't exist (wrong ID, wrong page) Check page context, verify ID
Script hangs / no response Infinite loop or unresolved promise Check for while(true) or missing await; ensure code terminates
"The node with id X does not exist" Parent instance was implicitly detached by a child detachInstance(), changing IDs Re-discover nodes by traversal from a stable (non-instance) parent frame

When the script succeeds but the result looks wrong

  1. Call get_metadata to check structural correctness (hierarchy, counts, positions).
  2. Call get_screenshot to check visual correctness. Look closely for cropped/clipped text (line heights cutting off content) and overlapping elements — these are common and easy to miss.
  3. Identify the discrepancy — is it structural (wrong hierarchy, missing nodes) or visual (wrong colors, broken layout, clipped content)?
  4. Write a targeted fix script that modifies only the broken parts — don't recreate everything.

For the full validation workflow, see Validation & Error Recovery.

7. Pre-Flight Checklist

Before submitting ANY use_figma call, verify:

  • Code uses return to send data back (NOT figma.closePlugin())
  • Code is NOT wrapped in an async IIFE (auto-wrapped for you)
  • return value includes structured data with actionable info (IDs, counts)
  • NO usage of figma.notify() anywhere
  • NO usage of console.log() as output (use return instead)
  • All colors use 0–1 range (not 0–255)
  • Fills/strokes are reassigned as new arrays (not mutated in place)
  • Page switches use await figma.setCurrentPageAsync(page) (sync setter throws)
  • layoutSizingVertical/Horizontal = 'FILL' is set AFTER parent.appendChild(child)
  • loadFontAsync() called BEFORE any text property changes
  • lineHeight/letterSpacing use {unit, value} format (not bare numbers)
  • resize() is called BEFORE setting sizing modes (resize resets them to FIXED)
  • For multi-step workflows: IDs from previous calls are passed as string literals (not variables)
  • New top-level nodes are positioned away from (0,0) to avoid overlapping existing content
  • ALL created/mutated node IDs are collected and included in the return value
  • Every async call (loadFontAsync, setCurrentPageAsync, importComponentByKeyAsync, etc.) is awaited — no fire-and-forget Promises

8. Discover Conventions Before Creating

Always inspect the Figma file before creating anything. Different files use different naming conventions, variable structures, and component patterns. Your code should match what's already there, not impose new conventions.

When in doubt about any convention (naming, scoping, structure), check the Figma file first, then the user's codebase. Only fall back to common patterns when neither exists.

Quick inspection scripts

List all pages and top-level nodes:

const pages = figma.root.children.map(p => `${p.name} id=${p.id} children=${p.children.length}`);
return pages.join('\n');

List existing components across all pages:

const results = [];
for (const page of figma.root.children) {
  await figma.setCurrentPageAsync(page);
  page.findAll(n => {
    if (n.type === 'COMPONENT' || n.type === 'COMPONENT_SET')
      results.push(`[${page.name}] ${n.name} (${n.type}) id=${n.id}`);
    return false;
  });
}
return results.join('\n');

List existing variable collections and their conventions:

const collections = await figma.variables.getLocalVariableCollectionsAsync();
const results = collections.map(c => ({
  name: c.name, id: c.id,
  varCount: c.variableIds.length,
  modes: c.modes.map(m => m.name)
}));
return results;

9. Reference Docs

Load these as needed based on what your task involves:

Doc When to load What it covers
gotchas.md Before any use_figma Every known pitfall with WRONG/CORRECT code examples
common-patterns.md Need working code examples Script scaffolds: shapes, text, auto-layout, variables, components, multi-step workflows
plugin-api-patterns.md Creating/editing nodes Fills, strokes, Auto Layout, effects, grouping, cloning, styles
api-reference.md Need exact API surface Node creation, variables API, core properties, what works and what doesn't
validation-and-recovery.md Multi-step writes or error recovery get_metadata vs get_screenshot workflow, mandatory error recovery steps
component-patterns.md Creating components/variants combineAsVariants, component properties, INSTANCE_SWAP, variant layout, discovering existing components, metadata traversal
variable-patterns.md Creating/binding variables Collections, modes, scopes, aliasing, binding patterns, discovering existing variables
text-style-patterns.md Creating/applying text styles Type ramps, font probing, listing styles, applying styles to nodes
effect-style-patterns.md Creating/applying effect styles Drop shadows, listing styles, applying styles to nodes
plugin-api-standalone.index.md Need to understand the full API surface Index of all types, methods, and properties in the Plugin API
plugin-api-standalone.d.ts Need exact type signatures Full typings file — grep for specific symbols, don't load all at once

10. Snippet examples

You will see snippets throughout documentation here. These snippets contain useful plugin API code that can be repurposed. Use them as is, or as starter code as you go. If there are key concepts that are best documented as generic snippets, call them out and write to disk so you can reuse in the future.

Files (skills)
  • agents
    • openai.yaml 452 B
      interface:
        display_name: "use_figma"
        short_description: "Load the required rules before calling use_figma"
        icon_small: "./assets/figma-small.svg"
        icon_large: "./assets/figma.png"
        default_prompt: "Use $figma-use and follow its rules before making any use_figma call."
      
      dependencies:
        tools:
          - type: "mcp"
            value: "figma"
            description: "Figma MCP server"
            transport: "streamable_http"
            url: "https://mcp.figma.com/mcp"
      
  • assets
    • figma-small.svg 818 B · in bundle
    • figma.png 1.6 KB · in bundle
    • icon.svg 964 B · in bundle
  • references
    • working-with-design-systems
      • maintainers.yml 303 B
        wwds-components--creating.md: mcp_server
        wwds-components--using.md: mcp_server
        wwds-components.md: mcp_server
        wwds-effect-styles.md: mcp_server
        wwds-text-styles.md: mcp_server
        wwds-variables--creating.md: mcp_server
        wwds-variables--using.md: mcp_server
        wwds-variables.md: mcp_server
        wwds.md: mcp_server
        
      • wwds-components--creating.md 2.1 KB
        # Working with design systems: Creating Components
        
        When creating Figma components, you need to start by understanding the source and its intent.
        
        If the user is asking you to create a component based on a design or specification, you need to understand the property model before you build anything. What variants are needed? What text, boolean, or instance swap properties exist? Getting the structure right upfront matters because restructuring a component after instances exist is destructive.
        
        If you are given a code component as reference (React props, tokens, etc.), your goal is to reflect the property surface as closely as makes sense in Figma's model. Not all code properties translate directly — hover and focus states are not props in web code, but they are variants in Figma. Understand those gaps and make deliberate decisions about how to represent them.
        
        Variants are the most important thing to get right. Each combination of variant values creates a node on the canvas. Redundant combinations still exist as explicit nodes — there is no way to conditionally exclude them. Define only the axes you actually need.
        
        Non-variant properties (text, boolean, instance swap) should be added after the variant structure is established. These are defined at the component/component set level and referenced by descendant nodes via `componentPropertyReferences`. Always connect them — a property that isn't wired to a descendant is invisible to users of the component.
        
        If the user asks you to make architectural decisions, lean toward fewer variants and more boolean/text properties where possible. Variants multiply combinatorially; the other property types do not. An optional slot property in code might be a combination of instance swap and boolean visibility.
        
        When naming properties, casing is less important since translation layers like Code Connect can do the mapping to represent the code form. Feel free to take a sentence or capitalized case approach for better readability in Figma.
        
        Keep in mind that components often need to be published and connected to Code Connect for the full design-to-code workflow to work. Creating the component is only one part of the system.
        
      • wwds-components--using.md 2 KB
        # Working with design systems: Using Components
        
        When using Figma components, you need to start by understanding the state of the source and the state of Figma.
        
        For the source, you need to know what component is being referenced. This could come from a component key, a node ID, a name, or a Code Connect mapping. If you have a component key from a design system library, prefer `importComponentByKeyAsync` over finding by name, since names are not unique. If you only have a name, search the page or use `search_design_system` to find the right match.
        
        For Figma, you need to know whether the component is local or in a library. Local components can be accessed directly by node ID. Published library components must be imported first — `importComponentByKeyAsync` or `importComponentSetByKeyAsync` — before an instance can be created.
        
        Before setting properties on an instance, read `componentPropertyDefinitions` from the main component first. Property names are not simple strings — TEXT, BOOLEAN, and INSTANCE_SWAP properties have a `#uid` suffix (e.g. `"Label#1234"`). Only VARIANT properties are plain names (e.g. `"Size"`). Using the wrong key in `setProperties` will silently do nothing.
        
        A component might have multiple text properties, which are not possible to derive from text node layer names. Look to the properties to help you understand what values to set, rather than thinking of setting text node characters directly.
        
        When you need to set a nested instance swap (e.g. an icon property), you need the component key of the swap target, not just its name. Import the target component and pass its node ID.
        
        Be aware that instances inside other instances are nested and changes made to a nested instance may be treated as overrides. If the intent is to change the default appearance, you need to modify the main component, not the instance.
        
        When selecting which variant to use, read the `componentProperties` on the instance to see the current state, and `componentPropertyDefinitions` on the main component to see all available options.
        
      • wwds-components.md 4 KB
        # Components
        
        Components overlap a lot with the idea of components in a codebase, but with some gaps and other Figma-specific use cases. Components in Figma can be reusable entities that do not have a comparable library pattern, or they can be published and distributed in a library that is aligned to a code forms.
        
        Properties can vary from code in different ways, but alignment to code can still happen without a direct relationship. For example, an interactive pattern in code (like a button) can have many states. A lot of these states (active, focused etc) would be expressed in Figma as variants, which is a concept more closely aligned to properties in a code library. In the case of web this is confusing since hover is not a prop, it is a pseudo selector. At the same time, a color variant might be perfectly aligned between design and code (a property in both places). These discrepancies are accounted for in translation with Figma's Code Connect (deterministic context mapping), but in the case of these tools, must be understood to be properly used.
        
        Figma has four property types, which can be inspected in the component definition's `componentPropertyDefinitions`. To fully understand the component, its descendants must be traversed. Property types include:
        
        - Variant
          - This is reflected as permutations of the component in a Component Set on the canvas. Each variant is explicitly visualized, including an redundant permutations ("Small + Primary + Disabled" may look the same as "Small Secondary Sisabled"). These permutations create different variants implicitly in Figma and it is handled through layer naming (`Variant=Primary,Size=Small,State=Disabled`).
        - Text/String
          - Text properties are stored on the component parent, but can be mapped to Text node descendants.
          - `node.componentPropertyReferences.characters` on a descendant text node are how you determine where the text property is referenced (can be multiple, though unlikely).
        - Boolean
          - Boolean properties are stored on the component parent, but can be mapped to any node descendant that can have its visibility toggled.
          - `node.componentPropertyReferences.visible` on a descendant node are how you determine where the boolean property is referenced.
        - Instance Swap
          - Instance swap properties are stored on the component parent, but can be mapped to Instance node descendants.
          - `node.componentPropertyReferences.mainComponent` on a descendant instance node are how you determine where the instance property is referenced. A classic example of this is an icon property.
        
        ## Descriptions
        
        Components, component sets, and instances all inherit `PublishableMixin`, which includes a writable `description` string. Setting a description is important for any component intended to be used by others — it appears in Figma's dev mode and component panel, and is surfaced in MCP context when reading component metadata.
        
        Descriptions should explain the component's intent and any non-obvious usage constraints. They are not a substitute for Code Connect annotations, but they are always visible without any tooling setup.
        
        ```js
        component.description =
          "Primary action button. Use for the single most important action on a page.";
        ```
        
        Variant components (children of a component set) also have a `description` field, but in practice the component set description is what users see. Set it on the component set, not on individual variant nodes.
        
        To read descriptions when auditing:
        
        ```js
        // Get all component sets and their descriptions
        figma.root
          .findAllWithCriteria({ types: ["COMPONENT_SET"] })
          .map((n) => ({ name: n.name, description: n.description }));
        ```
        
        ## Usage guidelines
        
        - [Creating components](wwds-components--creating.md): What you must consider when creating new components.
        - [Using components](wwds-components--using.md): What you must consider when trying to use the right components.
        
        ## Code patterns
        
        For runnable code examples (creating, importing, discovering, inspecting components), see [component-patterns.md](../component-patterns.md).
        
      • wwds-effect-styles.md 3.2 KB
        # Working with design systems: Effect Styles
        
        Effect styles in Figma are named, reusable definitions of one or more visual effects — drop shadows, inner shadows, and blurs. They are the closest equivalent to a shadow or elevation token in a design system.
        
        Effect styles are distinct from variables. There is no single variable type that represents a shadow. However, individual numeric and color properties within an effect _can_ be bound to variables, allowing shadow values to participate in a token system.
        
        ## Model
        
        An `EffectStyle` has one core writable property beyond the base style fields:
        
        | Property      | Type                    | Notes                                                 |
        | ------------- | ----------------------- | ----------------------------------------------------- |
        | `name`        | `string`                | Slash-delimited for grouping (e.g. `"Elevation/200"`) |
        | `effects`     | `ReadonlyArray<Effect>` | **Read-only array** — clone, modify, reassign         |
        | `description` | `string`                | Inherited from `BaseStyleMixin`                       |
        
        ### Effect types
        
        An `Effect` is a discriminated union. The most common types:
        
        | `type`            | Key properties                                                                                       |
        | ----------------- | ---------------------------------------------------------------------------------------------------- |
        | `DROP_SHADOW`     | `color: RGBA`, `offset: Vector`, `radius: number`, `spread: number`, `visible: boolean`, `blendMode` |
        | `INNER_SHADOW`    | Same as `DROP_SHADOW`                                                                                |
        | `LAYER_BLUR`      | `radius: number`, `visible: boolean`                                                                 |
        | `BACKGROUND_BLUR` | `radius: number`, `visible: boolean`                                                                 |
        
        All colors are in 0–1 range (`RGBA`), not 0–255.
        
        ### Variable bindings on effects
        
        Effect properties that can be bound to variables (via `setBoundVariableForEffect(effect, field, variable)` on a node, or inline when constructing):
        
        `color`, `radius`, `spread`, `offsetX`, `offsetY`
        
        Note: `setBoundVariableForEffect` returns a **new** effect object — you must capture it and reassign the `effects` array.
        
        ### Applying an effect style to a node
        
        Assign the style's `id` to the node's `effectStyleId`. The node's `effects` property will then reflect the style's values.
        
        ## Common gotchas
        
        - **`effects` is read-only**: You cannot mutate the array in place. Clone it, modify the clone, then reassign: `style.effects = [...style.effects, newEffect]`.
        - **Effects stack in order**: The order of effects in the array matters visually. Drop shadows render bottom-to-top.
        - **Colors are RGBA 0–1**: `{ r: 0, g: 0, b: 0, a: 0.15 }` — not hex, not 0–255.
        - **`getLocalEffectStyles()` is deprecated**: Always use `getLocalEffectStylesAsync()`.
        - **Styles are not automatically applied**: Creating an `EffectStyle` has no effect on any node until you assign its ID to a node.
        
        ## Code patterns
        
        For runnable code examples (listing, creating, applying effect styles), see [effect-style-patterns.md](../effect-style-patterns.md).
        
      • wwds-text-styles.md 5.9 KB
        # Working with design systems: Text Styles
        
        Text styles in Figma are named, reusable typography definitions. They are the closest equivalent to a type ramp in a design token library. A text style bundles font family, size, weight, line height, letter spacing, and other typographic properties into a single named entity that can be applied to text nodes.
        
        Text styles are distinct from variables. You cannot put typography into a single variable — there is no composite variable type. However, individual properties on a text style _can_ be bound to variables (e.g. binding `fontSize` to a size variable, or `fontFamily` to a string variable), which allows the style to participate in a token system.
        
        ## Model
        
        A `TextStyle` has the following writable properties:
        
        | Property           | Type             | Notes                                                                        |
        | ------------------ | ---------------- | ---------------------------------------------------------------------------- |
        | `name`             | `string`         | Slash-delimited for grouping (e.g. `"Heading/XL"`)                           |
        | `fontSize`         | `number`         | In pixels                                                                    |
        | `fontName`         | `FontName`       | `{ family: string, style: string }` — **font must be loaded before setting** |
        | `letterSpacing`    | `LetterSpacing`  | `{ value: number, unit: 'PIXELS' \| 'PERCENT' }`                             |
        | `lineHeight`       | `LineHeight`     | `{ value: number, unit: 'PIXELS' \| 'PERCENT' }` or `{ unit: 'AUTO' }`       |
        | `textCase`         | `TextCase`       | `'ORIGINAL' \| 'UPPER' \| 'LOWER' \| 'TITLE' \| 'SMALL_CAPS'`                |
        | `textDecoration`   | `TextDecoration` | `'NONE' \| 'UNDERLINE' \| 'STRIKETHROUGH'`                                   |
        | `paragraphSpacing` | `number`         |                                                                              |
        | `paragraphIndent`  | `number`         |                                                                              |
        | `description`      | `string`         | Inherited from `BaseStyleMixin`                                              |
        
        ### lineHeight and letterSpacing format
        
        These properties must be objects — not bare numbers:
        
        ```js
        // WRONG — bare number throws
        style.lineHeight = 1.5;
        style.letterSpacing = 0;
        
        // CORRECT
        style.lineHeight = { unit: "AUTO" }; // auto line height
        style.lineHeight = { value: 24, unit: "PIXELS" }; // fixed pixel height
        style.lineHeight = { value: 150, unit: "PERCENT" }; // 150% line height
        
        style.letterSpacing = { value: 0, unit: "PIXELS" }; // zero tracking
        style.letterSpacing = { value: -2, unit: "PIXELS" }; // tight tracking
        style.letterSpacing = { value: 5, unit: "PERCENT" }; // percent-based tracking
        ```
        
        When reading a `lineHeight` back, always check `unit` first — `{ unit: 'AUTO' }` has no `value` key.
        
        ### Variable bindings on text styles
        
        The following fields can be bound to variables via `style.setBoundVariable(field, variable)`:
        
        `fontFamily`, `fontSize`, `fontStyle`, `fontWeight`, `letterSpacing`, `lineHeight`, `paragraphSpacing`, `paragraphIndent`
        
        To unbind: `style.setBoundVariable(field, null)`
        
        **Important: `setBoundVariable` is NOT available on `TextStyle` in headless `use_figma` mode.**
        
        It is only available in interactive plugin context (UI plugins, Figma editor). When running through `use_figma` (MCP, assistant headless runtime), calling `ts.setBoundVariable(...)` will throw `"not a function"`. In this context, set raw values directly instead:
        
        ```js
        // In use_figma (headless) — variable binding not available
        const ts = figma.createTextStyle();
        ts.fontSize = 24; // set directly; cannot bind to a variable
        
        // In a real interactive plugin — variable binding works
        const ts = figma.createTextStyle();
        ts.setBoundVariable("fontSize", fontSizeVariable);
        ```
        
        If live variable binding on text styles is required, the recommended approach is to:
        
        1. Create the text styles with raw values via `use_figma`
        2. Open the file in Figma and bind variables interactively via the Styles panel, OR
        3. Use an interactive plugin that runs in the Figma editor (not headless)
        
        ### Applying a text style to a node
        
        Once you have a `TextStyle`, apply it to a `TextNode` by assigning its `id` to the node's `textStyleId` property. You can also use the async setter `setTextStyleIdAsync(id)`. Setting `textStyleId` on a node does **not** require the font to be loaded — only editing the text content or font properties directly does.
        
        ## Common gotchas
        
        - **Font must be loaded before setting `fontName`**: Call `await figma.loadFontAsync({ family, style })` before creating or modifying a text style's font.
        - **Font style names are file-dependent**: Font style names like `"SemiBold"` vs `"Semi Bold"` vary by font provider and Figma file. Always probe by calling `loadFontAsync` and catching errors to discover the correct style string rather than guessing.
        - **`setBoundVariable` not available headless**: `TextStyle.setBoundVariable()` throws `"not a function"` in `use_figma` / headless mode. Set raw values instead and bind interactively if needed.
        - **Styles are not automatically applied**: Creating a `TextStyle` has no effect on any node until you assign its ID to a text node.
        - **`getLocalTextStyles()` is deprecated**: Always use `getLocalTextStylesAsync()`.
        - **Names are not unique**: Two text styles can share the same name. Match by ID or `key` when looking up a known style, not by name alone.
        - **Slash grouping is visual only**: `"Heading/XL"` and `"HeadingXL"` are different names; the slash is just a UI affordance.
        - **`lineHeight` and `letterSpacing` must be objects**: `style.lineHeight = 1.5` throws. Always use `{ value, unit }` format or `{ unit: 'AUTO' }`.
        
        ## Code patterns
        
        For runnable code examples (listing, creating, probing fonts, type ramps, applying styles), see [text-style-patterns.md](../text-style-patterns.md).
        
      • wwds-variables--creating.md 1.5 KB
        # Working with design systems: Creating Variables
        
        When creating Figma variables, you need to start by understanding the state of the source data.
        
        If the user is asking you to create variables based on values, they likely want you to indicate the structure. Whether or not you use semantic aliasing primitive will be based on the inputs you are given about the source data.
        
        If you are given code inputs (JSON, CSS, etc) your goal should be to reflect the existing patterns as closely as possible, but also embrace the design context as distinct from code. For example, casing is less important since you have code syntax that can directly represent the code form. Feel free to take a sentence or capitalized case approach for better readability in Figma.
        
        It is important to understand the underlying structure before you create anything. If there is an implied aliased setup, you want to get that right. You may also need to anticipate modes to know how to split things up. Sizes and Colors likely have different mode requirements in complex systems, so you want to consider that as you create the structure.
        
        If someone asks you to just make a decision based on best practices, that answer will be relative to the complexity of the environment. A simple theme is great best practice for simple needs. Similarly, a complex extended collection setup for someone on an enterprise plan might also be best practice as well.
        
        Keep in mind that systems might also require you to handle text and effect styles for some of the things specified in token libraries.
        
      • wwds-variables--using.md 1.9 KB
        # Working with design systems: Using Variables
        
        When using Figma variables, you need to start by understanding the state of the source and the state of Figma.
        
        For the source, you need to know the breadth of variables code representation. CSS, JSON, theme providers etc will all be able to indicate what the user will expect you to cover in Figma. Some beginner users might not even know what does and doesn't exist in Figma, and if you cant discover that on your own, you will need their help making the right decision.
        
        For Figma, you need to know what collections exist, what their modes are, and what values and names and code syntaxes are in them. This will help you make sure you are using the right things. For properties that "should" have variables but don't, you likely will need to ask the user what to do. Your understanding of Figma's current state should come first.
        
        You can use code syntax and your understanding of the environment you are expected to be referencing to know which variable in Figma to use. You can also use Figma's variable scopes as indicators if they are specified. It is best to audit those up front.
        
        When using variables you should also be aware of mode mismatches, the default mode in Figma may not be the mode referenced by the user in their expectations. Similarly, many collections may refer to values, but the most specific collection is what you should be using. For example, a semantic collection that aliases a primitive collection, the semantic collection would be what you reference. A component token collection (eg. button/background/primary) might alias a semantic collection, and it is the component collection you need to reference. In some other examples, there may be no aliasing and you're simply value matching.
        
        Gap and padding values for frames are really important and often have to be interpreted semantically or based on layout component values.
        
      • wwds-variables.md 4.6 KB
        # Working with design systems: Variables
        
        Variables overlap a lot with the idea of tokens in a codebase, but with some gaps and other Figma-specific use cases. Variables are single value, number, string, color, boolean.
        
        In Figma you can do conditional logic and use variables to get basic prototyping functionality. String values can also be used as sophisticated placeholder setups that have different modes for different languages. Not everything you use a variable for in Figma would be used exactly the same way in code. However, for design systems, they are often synced to code in some way.
        
        One gap is the lack of composite tokens. You can't put a box shadow behind a single variable. That is an [effect style](wwds-effect-styles.md), but style values can be bound to variables. Similarly for a type ramp, you have to use [Text Styles](wwds-text-styles.md).
        
        ## Model
        
        ### Collections
        
        Collections can be thought of a groups in Figma. An example Collection would be "Colors" where there might be a light and dark "Mode." Each value would have two definitions.
        
        ### Extended Collections
        
        Extended collections allow you to create a colleciton based on another collection and only override _some_ of the values. Just like inheritance and overrides in CSS. This aligns well for scenarios like branded color themes.
        
        ### Modes
        
        Modes in Figma can be thought of like light and dark, but users can specify modes for anything, including sizes, languages (string variables exist in Figma too).
        
        ### Aliasing
        
        Aliasing in Figma variables is simply when you point a variable to another variable. Common example is pointing a semantic variable to a primitive variable. Some teams also do component level tokens which adds a third component specific layer.
        
        **Decision rule:** If the source data has two tiers (primitives + semantics), create all primitives first, then create semantic variables that alias into them. If the source data is a single flat tier, create flat variables with no aliases. When in doubt, ask.
        
        ### Code Syntax
        
        Code syntax is a surface area in Figma for codebase translation context. You can set WEB, iOS, and ANDROID code syntax on any variable, and when that variable is referenced in other places (visually in Figma's dev mode, as design context via MCP), this codebase form will appear. These are best thought of as "instance" documentation, eg. `var(--the-thing)` instead of `--the-thing` in the case of CSS.
        
        ### Scope
        
        `variable.scopes: VariableScope[]` specifies which properties in Figma the variable can be used for. This is important when you create and when you use variables. It is always better to use scopes than not or to set it to be `ALL_SCOPES`. The more specific the better, but not all variable collections are complex enough to account for precision here.
        
        Common scope values:
        
        - `ALL_SCOPES` — unrestricted; use when precision isn't required
        - `FILL_COLOR`, `STROKE_COLOR` — color bindings
        - `TEXT_CONTENT` — string variables for text layers
        - `FONT_SIZE`, `FONT_WEIGHT`, `LINE_HEIGHT`, `LETTER_SPACING` — typography
        - `CORNER_RADIUS`, `WIDTH_HEIGHT`, `GAP` — layout/spacing
        - `OPACITY` — layer opacity
        
        ### Grouping
        
        Variable names in Figma are slash delimited and each slash represents a group that is visualized in Figma. When you are doing matching, consider a part of a code prefix might be the name of the collection, not a top level group. Sometimes you will have prefixes in code that aren't in Figma, and that can be ok, just be sure to ask if it is unclear. You can always validate existing variables by referencing the code syntax.
        
        ## Common gotchas
        
        - **`createVariableCollection` always creates a default mode** — you will need to rename it (or delete it and add your own) rather than creating from scratch.
        - **Duplicate variable names throw silently** — Figma does not error; it creates a second variable with the same name. Always check for existence before creating.
        - **Variable aliases require the target to be in the same file** — cross-file aliasing is not supported via the plugin API. If you need to alias to a library variable, import it first.
        - **`setValueForMode` with an alias requires the exact shape** — `{ type: 'VARIABLE_ALIAS', id: '<variableId>' }`. Any deviation will silently set the wrong value or throw.
        
        ## Usage guidelines
        
        - [Creating variables](wwds-variables--creating.md): What you must consider when creating new variables.
        - [Using variables](wwds-variables--using.md): What you must consider when trying to use the right variables.
        
        ## Code patterns
        
        For runnable code examples (creating collections, binding variables, scopes, aliasing, discovering existing variables), see [variable-patterns.md](../variable-patterns.md).
        
      • wwds.md 5.1 KB
        # Working with design systems
        
        When working with design systems in Figma, there can be many nuances when deciding how to do the right thing. Figma's model for patterns is form-agnostic, this is one of its strengths, allowing people to refer to a pattern in a spec that may take distinct forms in different codebases. However, this can result in complex procedures and nuances when translating something to Figma and back. Figma has components, tokens, and other reusable patterns (text and effect styles, prototyping actions, etc). The way that Figma's paradigms function can be difficult to translate one to one.
        
        To make translation of patterns work between design and code forms, it is important that teams think about alignment while also embracing the function of representation (design) and implementation (production) forms independently as complementary pieces of a shared puzzle.
        
        For the process of design, the desirable state of a system is something that is structured with experimentation in mind, something that is highly visual, easy to iterate, test, and confirm new ideas. Depending on the product and team, exploration might be mandatory to be done within the confines of an existing system, for other scenarios, exploration of new territory is the priority, a place for the system to grow into, or a new system to be made. Figma's platform allows for teams to validate, align, and collaborate on new ideas, then solidify them in product designs, which are ultimately specs. That work is supported by design libraries and that process can include code in prototypes and other less permanent forms as much as it does Figma's native paradigms.
        
        For the process of implementation, the desirable state for production code is rigidity, efficiency, and related to secure data and functional layers. Developer experience implementing a design and the designer experience surfacing and committing to an idea are paths from distinct points of to the same shared outcome. Their optimization looks different, and that is reflected when you engage with Figma's APIs.
        
        The key is not to avoid gaps, but to make sure they are definitively bridgable. Translation layers help agents and people go between representational and production forms.
        
        The Figma paradigms you will need to understand when working with design systems. In each file below there will be further links to instructions for using and creating:
        
        - [Components](wwds-components.md)
        - [Variables](wwds-variables.md)
        - [Effect Styles](wwds-effect-styles.md)
        - [Text Styles](wwds-text-styles.md)
        
        Things you might be asked to do with respect to design systems:
        
        - Create patterns in Figma that match patterns in code
          - Likely (but not exclusively) to get up to speed so that visual riffing can be done in Figma
          - Create variables based on a stylesheet, JSON format, some other theme definition
          - Create Figma text styles that match a type hierarchy defined somewhere
          - Create components based on existing code components
        - Sync between code and design forms
          - Making sure that Figma's concepts match a production form
        - Use an existing Figma design library to create something
          - This something could be matching an existing code form, an image, or just a prompt
        - Clean up a design to match some code pattern
        
        ## Things to remember
        
        Many people will use these tools to try out ideas, and not everything you get asked to do will feel realistic for the environment you are running in. It is important to contextualize that, but then also know when you are definitively working in a production environment and there is a very real task you need to perform consistently.
        
        Not everyone asking you to do something knows what they should be doing. You must figure out if the request is to generically perform design systems actions, uphold existing the rules that are codified in Figma or in a codebase, demonstrate an idea, enforce existing guidelines, etc.
        
        Not every environment you are working in has the same degree of expertise and maturity. Some systems will be very complex and the priority and have a lot of things to parse through to get to the right outcome. Some scenarios will be very immature and even starting from scratch. Something as simple as creating a component could be very elementary or very sophisticated depending on the environment. The instructions you find here are attempting to be unbiased.
        
        For example, how you reflect the "hover" state of a button could be left entirely up to you to make a reasonable decision for a user that is playing around with getting a decent example scaffolded using best practices, but it could also be something that exists definitively in the codebase and you need to go match it. That codebase definition could be refering to design tokens that do not yet exist in code that change dark and light mode values. In this second example you are now needing to do a bunch of variables work just to add a hover state to a component with proper dark and light mode support, where in the first scenario, you can kinda just do whatever is easiest. This is the line you will be walking, and making good judgement here is about doing whatever is the smartest thing in the environment you are in.
        
    • api-reference.md 10.9 KB
      # Figma Plugin API Reference
      
      > Part of the [use_figma skill](../SKILL.md). What works and what doesn't in the `use_figma` environment.
      
      ## Contents
      
      - Node Creation
      - Grouping and Boolean Operations
      - Library Imports
      - Variables API
      - Core Properties
      - Node Manipulation
      - Descriptions and Documentation Links
      - SVG and Images
      - Utilities and Plugin Lifecycle
      - Node Traversal
      - Unsupported APIs
      
      
      ## Node Creation (Design Mode)
      
      ```js
      figma.createRectangle()
      figma.createFrame()
      figma.createComponent()         // Creates a ComponentNode
      figma.createText()
      figma.createEllipse()
      figma.createStar()
      figma.createLine()
      figma.createVector()
      figma.createPolygon()
      figma.createBooleanOperation()
      figma.createSlice()
      figma.createPage()              // Page node can be created, but child persistence is limited in headless mode
      figma.createSection()
      figma.createTextPath()
      ```
      
      ## Grouping & Boolean Operations
      
      ```js
      figma.group(nodes, parent, index?)              // Group nodes
      figma.flatten(nodes, parent?, index?)           // Flatten to vector
      figma.union(nodes, parent?, index?)             // Boolean union
      figma.subtract(nodes, parent?, index?)          // Boolean subtract
      figma.intersect(nodes, parent?, index?)         // Boolean intersect
      figma.exclude(nodes, parent?, index?)           // Boolean exclude
      figma.combineAsVariants(components, parent?)    // Combine ComponentNodes into ComponentSet (Design/Sites only)
      ```
      
      ## Library Component Import
      
      These methods import components from **team libraries** (not the same file you're working in). For components in the current file, use `use_figma` with `figma.getNodeByIdAsync()` or `findOne()`/`findAll()` to locate them directly.
      
      ```js
      // Import a published component from a team library by key
      const comp = await figma.importComponentByKeyAsync("COMPONENT_KEY")
      const instance = comp.createInstance()
      
      // Import a published component set from a team library by key
      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()
      ```
      
      ## Library Style Import (Team Libraries)
      
      These methods import styles from **team libraries** (not the same file). For styles in the current file, use `figma.getLocalPaintStyles()`, `figma.getLocalTextStyles()`, etc.
      
      ```js
      // Import a published style from a team library by key
      const style = await figma.importStyleByKeyAsync("STYLE_KEY")
      
      // Apply the imported style to a node
      await node.setFillStyleIdAsync(style.id)    // for PaintStyle as fill
      await node.setStrokeStyleIdAsync(style.id)  // for PaintStyle as stroke
      await node.setTextStyleIdAsync(style.id)    // for TextStyle
      await node.setEffectStyleIdAsync(style.id)  // for EffectStyle
      await node.setGridStyleIdAsync(style.id)    // for GridStyle
      ```
      
      ## Library Variable Import (Team Libraries)
      
      This imports variables from **team libraries** (not the same file). For variables in the current file, use `figma.variables.getLocalVariables()` or `figma.variables.getVariableById()`.
      
      ```js
      // Import a published variable from a team library by key
      const variable = await figma.variables.importVariableByKeyAsync("VARIABLE_KEY")
      
      // Bind the imported variable to node properties
      node.setBoundVariable("width", variable)           // FLOAT variable
      
      // Bind to fills/strokes (COLOR variable) — returns a NEW paint, must capture it
      const newPaint = figma.variables.setBoundVariableForPaint(paintCopy, "color", variable)
      node.fills = [newPaint]
      ```
      
      ## Variables API
      
      ```js
      // Collections
      const collection = figma.variables.createVariableCollection("Name")
      collection.name                           // Get/set name
      collection.modes                          // Array of {modeId, name} — starts with 1 mode
      collection.addMode("Dark")               // Returns new modeId string
      collection.renameMode(modeId, "Light")
      
      // Variables
      const variable = figma.variables.createVariable("name", collection, "COLOR")
      //                                                       ^ object or ID string
      // resolvedType: "COLOR" | "FLOAT" | "STRING" | "BOOLEAN"
      variable.setValueForMode(modeId, value)
      
      // Scopes — controls where variable appears in property pickers
      variable.scopes = ["FRAME_FILL", "SHAPE_FILL"]   // only fill pickers
      variable.scopes = ["TEXT_FILL"]                    // only text color picker
      variable.scopes = ["STROKE_COLOR"]                 // only stroke picker
      variable.scopes = []                               // hidden from all pickers (use for primitives)
      // All valid scope values:
      //   ALL_SCOPES, TEXT_CONTENT, CORNER_RADIUS, WIDTH_HEIGHT, GAP,
      //   ALL_FILLS, FRAME_FILL, SHAPE_FILL, TEXT_FILL,
      //   STROKE_COLOR, STROKE_FLOAT, EFFECT_FLOAT, EFFECT_COLOR,
      //   OPACITY, FONT_FAMILY, FONT_STYLE, FONT_WEIGHT, FONT_SIZE,
      //   LINE_HEIGHT, LETTER_SPACING, PARAGRAPH_SPACING, PARAGRAPH_INDENT
      
      // Querying
      figma.variables.getVariableById(id)
      figma.variables.getLocalVariables(resolvedType?)
      figma.variables.getVariableCollectionById(id)
      figma.variables.getLocalVariableCollections()
      
      // Binding variables to paints (COLOR variables)
      const newPaint = figma.variables.setBoundVariableForPaint(paintCopy, "color", variable)
      // ⚠️ Returns a NEW paint — must capture return value!
      node.fills = [newPaint]
      
      // Binding variables to effects (COLOR/FLOAT variables)
      const newEffect = figma.variables.setBoundVariableForEffect(effectCopy, field, variable)
      // field for shadows: "color" (COLOR), "radius" | "spread" | "offsetX" | "offsetY" (FLOAT)
      // field for blurs: "radius" (FLOAT)
      // ⚠️ Returns a NEW effect — must capture return value!
      node.effects = [newEffect]
      
      // Binding variables to layout grids (FLOAT variables)
      const newGrid = figma.variables.setBoundVariableForLayoutGrid(gridCopy, field, variable)
      // field: "sectionSize" | "offset" | "count" | "gutterSize"
      // ⚠️ Returns a NEW layout grid — must capture return value!
      node.layoutGrids = [newGrid]
      
      // Binding variables to node properties (FLOAT/STRING/BOOLEAN)
      // Layout & sizing (FLOAT):
      node.setBoundVariable("width", variable)
      node.setBoundVariable("height", variable)
      node.setBoundVariable("minWidth", variable)
      node.setBoundVariable("maxWidth", variable)
      node.setBoundVariable("minHeight", variable)
      node.setBoundVariable("maxHeight", variable)
      node.setBoundVariable("paddingLeft", variable)
      node.setBoundVariable("paddingRight", variable)
      node.setBoundVariable("paddingTop", variable)
      node.setBoundVariable("paddingBottom", variable)
      node.setBoundVariable("itemSpacing", variable)
      node.setBoundVariable("counterAxisSpacing", variable)
      // Corner radii (FLOAT) — use individual corners, NOT cornerRadius:
      node.setBoundVariable("topLeftRadius", variable)
      node.setBoundVariable("topRightRadius", variable)
      node.setBoundVariable("bottomLeftRadius", variable)
      node.setBoundVariable("bottomRightRadius", variable)
      // Other (FLOAT):
      node.setBoundVariable("opacity", variable)
      node.setBoundVariable("strokeWeight", variable)
      // ⚠️ fontSize, fontWeight, lineHeight are NOT bindable via setBoundVariable
      // — set these directly as values on text nodes
      
      // Aliases
      figma.variables.createVariableAlias(variable)
      
      // Explicit modes — CRITICAL for variant components
      node.setExplicitVariableModeForCollection(collectionId, modeId)
      // Without this, all nodes use the default (first) mode of the collection
      ```
      
      ## Core Properties
      
      ```js
      figma.root                      // DocumentNode
      figma.currentPage               // Current page (read-only in use_figma; sync setter throws)
      figma.setCurrentPageAsync(page) // Switch page and load its content (MUST await)
      figma.fileKey                   // File key string
      figma.mixed                     // Mixed sentinel value
      ```
      
      ## Node Manipulation
      
      ```js
      // Fills & Strokes (read-only arrays — must clone)
      node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
      node.strokes = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }]
      node.strokeWeight = 1
      node.strokeAlign = 'INSIDE'             // 'INSIDE' | 'CENTER' | 'OUTSIDE'
      
      // Effects
      node.effects = [{ type: 'DROP_SHADOW', color: {r:0,g:0,b:0,a:0.25}, offset:{x:0,y:4}, radius:4, visible:true }]
      
      // Layout
      node.layoutMode = 'HORIZONTAL'          // 'NONE' | 'HORIZONTAL' | 'VERTICAL'
      node.primaryAxisAlignItems = 'CENTER'    // 'MIN' | 'CENTER' | 'MAX' | 'SPACE_BETWEEN'
      node.counterAxisAlignItems = 'CENTER'    // 'MIN' | 'CENTER' | 'MAX' | 'BASELINE'
      node.paddingLeft = 8
      node.paddingRight = 8
      node.paddingTop = 4
      node.paddingBottom = 4
      node.itemSpacing = 4
      node.layoutSizingHorizontal = 'HUG'     // 'FIXED' | 'HUG' | 'FILL'
      node.layoutSizingVertical = 'HUG'       // 'FIXED' | 'HUG' | 'FILL'
      
      // Sizing
      node.resize(width, height)                     // ⚠️ Resets sizing modes to FIXED
      node.resizeWithoutConstraints(width, height)   // Doesn't affect constraints
      
      // Corner radius
      node.cornerRadius = 8
      
      // Visibility & Opacity
      node.visible = true
      node.opacity = 0.5
      
      // Naming & Hierarchy
      node.name = "My Node"
      parent.appendChild(child)
      parent.insertChild(index, child)
      node.remove()
      ```
      
      ## Descriptions & Documentation Links
      
      ```js
      // Description — plain text, shown in Figma's component panel
      node.description = "A short summary of this component's purpose and usage."
      
      // Documentation links — array of {uri, label} shown as clickable links
      componentSet.documentationLinks = [
        { uri: "https://example.com/docs", label: "Component Docs" }
      ]
      // ⚠️ uri MUST be a valid URL (https://...) — relative paths will throw
      ```
      
      ## SVG Import
      
      ```js
      const svgNode = figma.createNodeFromSvg('<svg>...</svg>')
      ```
      
      ## Images
      
      ```js
      const image = figma.createImage(uint8Array)
      node.fills = [{ type: 'IMAGE', scaleMode: 'FILL', imageHash: image.hash }]
      ```
      
      ## Utilities
      
      ```js
      figma.base64Encode(uint8Array)     // Uint8Array → base64 string
      figma.base64Decode(base64String)   // base64 string → Uint8Array
      figma.createComponentFromNode(node) // Convert existing node to component (Design/Sites only)
      ```
      
      ## Plugin Lifecycle
      
      ```js
      figma.closePlugin("message")                // Close and return a message to the agent (success)
      figma.closePluginWithFailure("error msg")   // Close with error — ALWAYS use in catch blocks
      ```
      
      ## Node Traversal
      
      ```js
      node.findAll(pred?)            // Find all descendants matching predicate
      node.findOne(pred?)            // Find first descendant matching predicate
      node.findChildren(pred?)       // Find direct children matching predicate
      node.findChild(pred?)          // Find first direct child matching predicate
      node.children                  // Direct children array
      node.parent                    // Parent node
      ```
      
      ---
      
      ## What Does NOT Work
      
      | API | Status |
      |-----|--------|
      | `figma.notify()` | **Throws "not implemented"** — most common mistake |
      | `figma.showUI()` | No-op (silently ignored) |
      | `figma.openExternal()` | No-op (silently ignored) |
      | `figma.listAvailableFontsAsync()` | Not implemented |
      | `figma.loadAllPagesAsync()` | Not implemented |
      | `figma.variables.extendLibraryCollectionByKeyAsync()` | Not implemented |
      | `figma.teamLibrary.*` | Not implemented (requires LiveGraph) |
      
    • common-patterns.md 15.7 KB
      # 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())
        }
      })()
      ```
      
    • component-patterns.md 18.1 KB
      # Component & Variant API Patterns
      
      > Part of the [use_figma skill](../SKILL.md). How to correctly use the Plugin API for components, variants, and component properties.
      >
      > For design system context (when to use variants vs properties, code-to-Figma translation, property model), see [wwds-components](working-with-design-systems/wwds-components.md).
      
      ## Contents
      
      - Creating a Component
      - Combining Components into a Component Set (Variants)
      - Laying Out Variants After combineAsVariants (Required)
      - Component Properties: addComponentProperty API
      - Linking Properties to Child Nodes (Required)
      - INSTANCE_SWAP: Avoiding Variant Explosion
      - Discovering Existing Conventions in the File
      - Importing Components by Key
      - Working with Instances (finding variants, setProperties, text overrides, detachInstance)
      
      
      ## Creating a Component
      
      `figma.createComponent()` returns a `ComponentNode`, which behaves like a `FrameNode` but can be published, instanced, and combined into variant sets.
      
      ```javascript
      const comp = figma.createComponent();
      comp.name = "MyComponent";
      comp.layoutMode = "HORIZONTAL";
      comp.primaryAxisAlignItems = "CENTER";
      comp.counterAxisAlignItems = "CENTER";
      comp.paddingLeft = 12;
      comp.paddingRight = 12;
      comp.layoutSizingHorizontal = "HUG";
      comp.layoutSizingVertical = "HUG";
      comp.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.36, b: 0.96 } }];
      ```
      
      ## Combining Components into a Component Set (Variants)
      
      `figma.combineAsVariants(components, parent)` takes an array of `ComponentNode`s (not frames — frames will throw) and groups them into a `ComponentSetNode`.
      
      Variant names use a `Property=Value` format. Every unique combination must exist as a child component — missing ones show as blank gaps in the variant picker.
      
      ```javascript
      // Each component's name encodes its variant properties
      const comp1 = figma.createComponent();
      comp1.name = "size=md, style=primary";
      const comp2 = figma.createComponent();
      comp2.name = "size=md, style=secondary";
      
      const componentSet = figma.combineAsVariants([comp1, comp2], figma.currentPage);
      componentSet.name = "Button";
      ```
      
      **Before creating variants, inspect the file** for existing naming patterns. Different files use different conventions (`State=Default` vs `state=default` vs `State/Default`). Always match what's already there.
      
      ## Laying Out Variants After combineAsVariants (Required)
      
      After `combineAsVariants`, all children stack at `(0, 0)`. You **must** position them or the component set will appear as a single collapsed element with all variants overlapping.
      
      ```javascript
      const cs = figma.combineAsVariants(components, figma.currentPage);
      
      // Simple row layout
      cs.children.forEach((child, i) => {
        child.x = i * 150;
        child.y = 0;
      });
      
      // CRITICAL: resize the component set 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);
      ```
      
      For multi-axis variants (e.g., size × style × state), parse the child's name to determine grid position:
      
      ```javascript
      for (const child of cs.children) {
        const props = Object.fromEntries(
          child.name.split(', ').map(p => p.split('='))
        );
        const col = stateValues.indexOf(props.state);
        const row = styleValues.indexOf(props.style);
        child.x = col * colWidth;
        child.y = row * rowHeight;
      }
      ```
      
      ## Component Properties: addComponentProperty API
      
      `addComponentProperty` adds a TEXT, BOOLEAN, or INSTANCE_SWAP property to a component. It returns a **string key** (e.g., `"label#4:0"`) — never hardcode or guess this key.
      
      ```javascript
      // Returns the key as a string — capture it!
      const labelKey = comp.addComponentProperty('Label', 'TEXT', 'Default text');
      const showIconKey = comp.addComponentProperty('Show Icon', 'BOOLEAN', true);
      const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', iconComponentId);
      ```
      
      **Timing**: Add component properties to each variant component **before** calling `combineAsVariants`. After combining, the component set inherits all properties from its children. Do not add properties to the `ComponentSetNode` directly.
      
      ## Linking Properties to Child Nodes (Required)
      
      A property that is added but not linked to a child node does **nothing**. You must set `componentPropertyReferences` on the child:
      
      ```javascript
      // TEXT property → link to a text node's characters
      const labelKey = comp.addComponentProperty('Label', 'TEXT', 'Button');
      const textNode = figma.createText();
      textNode.characters = "Button";
      comp.appendChild(textNode);
      textNode.componentPropertyReferences = { characters: labelKey };
      
      // BOOLEAN + INSTANCE_SWAP → link to an instance node
      const showIconKey = comp.addComponentProperty('Show Icon', 'BOOLEAN', true);
      const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', iconComp.id);
      const iconInstance = iconComp.createInstance();
      comp.appendChild(iconInstance);
      iconInstance.componentPropertyReferences = {
        visible: showIconKey,        // BOOLEAN controls show/hide
        mainComponent: iconSlotKey   // INSTANCE_SWAP controls which component
      };
      ```
      
      **Valid `componentPropertyReferences` keys:**
      - `characters` — TEXT property on a TextNode
      - `visible` — BOOLEAN property (any node)
      - `mainComponent` — INSTANCE_SWAP property on an InstanceNode
      
      ## INSTANCE_SWAP: Avoiding Variant Explosion
      
      When a component has many possible sub-elements (e.g., 30 different icons), **never** create a variant per sub-element. Use a single INSTANCE_SWAP property instead — the user picks from any compatible component at design time.
      
      ```javascript
      // Create icon as its own ComponentNode
      const iconComp = figma.createComponent();
      iconComp.name = "Icon/Search";
      iconComp.resize(24, 24);
      const svgNode = figma.createNodeFromSvg('<svg>...</svg>');
      iconComp.appendChild(svgNode);
      
      // Use it as the default for INSTANCE_SWAP
      const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', iconComp.id);
      const instance = iconComp.createInstance();
      comp.appendChild(instance);
      instance.componentPropertyReferences = { mainComponent: iconSlotKey };
      ```
      
      This works for icons, avatars, badges, or any swappable nested element.
      
      ## Discovering Existing Conventions in the File
      
      **Always inspect the file before creating components.** Different files have different naming styles, structures, and conventions. Your code should match what's already there.
      
      ### List all existing components across all pages
      
      ```javascript
      (async () => {
        try {
          const results = [];
          for (const page of figma.root.children) {
            await figma.setCurrentPageAsync(page);
            page.findAll(n => {
              if (n.type === 'COMPONENT') results.push(`[${page.name}] ${n.name} (COMPONENT) id=${n.id}`);
              if (n.type === 'COMPONENT_SET') results.push(`[${page.name}] ${n.name} (COMPONENT_SET) id=${n.id}`);
              return false;
            });
          }
          figma.closePlugin(results.join('\n'));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ### Inspect an existing component set's variant naming pattern
      
      ```javascript
      (async () => {
        try {
          const cs = await figma.getNodeByIdAsync('COMPONENT_SET_ID');
          const variantNames = cs.children.map(c => c.name);
          const propDefs = cs.componentPropertyDefinitions;
          figma.closePlugin(JSON.stringify({ variantNames, propDefs }));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ### Find existing components in the file
      
      ```javascript
      (async () => {
        try {
          const components = [];
          for (const page of figma.root.children) {
            await figma.setCurrentPageAsync(page);
            page.findAll(n => {
              if (n.type === 'COMPONENT') {
                components.push({ name: n.name, id: n.id, page: page.name, w: n.width, h: n.height });
              }
              return false;
            });
          }
          figma.closePlugin(JSON.stringify(components));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ## Importing Components 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.
      
      ```javascript
      // Import a component from a team library
      const comp = await figma.importComponentByKeyAsync("COMPONENT_KEY");
      const instance = comp.createInstance();
      
      // Import a component set from a team library and pick a variant
      const set = await figma.importComponentSetByKeyAsync("COMPONENT_SET_KEY");
      const variant = set.children.find(c =>
        c.type === "COMPONENT" && c.name.includes("size=md")
      ) || set.defaultVariant;
      const variantInstance = variant.createInstance();
      ```
      
      ## Working with Instances
      
      ### Finding the right variant in a component set
      
      Parse variant names to match on multiple properties simultaneously:
      
      ```javascript
      const compSet = await figma.importComponentSetByKeyAsync("KEY");
      
      const variant = compSet.children.find(c => {
        const props = Object.fromEntries(
          c.name.split(', ').map(p => p.split('='))
        );
        return props.variant === "primary" && props.size === "md";
      }) || compSet.defaultVariant;
      
      const instance = variant.createInstance();
      ```
      
      ### Setting variant properties on an instance
      
      After creating an instance from a component set, you can set variant properties via `setProperties`:
      
      ```javascript
      const instance = defaultVariant.createInstance();
      instance.setProperties({
        "variant": "primary",
        "size": "medium"
      });
      ```
      
      ### Overriding text in a component instance
      
      **Always discover component properties BEFORE writing text overrides.** Components expose text as `TEXT`-type component properties, and `setProperties()` is the correct way to override them. Direct `node.characters` changes on property-managed text may be overridden by the component property system on render.
      
      **Step 1: Inspect componentProperties on a sample instance:**
      
      ```javascript
      const instance = comp.createInstance();
      const propDefs = instance.componentProperties;
      // Returns e.g.: { "Label#2:0": { type: "TEXT", value: "Button" }, "Has Icon#4:64": { type: "BOOLEAN", value: true } }
      figma.closePlugin(JSON.stringify(propDefs));
      ```
      
      Also check nested instances — a parent component may not expose text properties directly, but its nested child instances might:
      
      ```javascript
      const nestedInstances = instance.findAll(n => n.type === "INSTANCE");
      const nestedProps = nestedInstances.map(ni => ({
        name: ni.name,
        id: ni.id,
        properties: ni.componentProperties
      }));
      ```
      
      **Step 2: Use setProperties() for TEXT-type properties:**
      
      ```javascript
      const instance = comp.createInstance();
      const propDefs = instance.componentProperties;
      for (const [key, def] of Object.entries(propDefs)) {
        if (def.type === "TEXT") {
          instance.setProperties({ [key]: "New text value" });
        }
      }
      ```
      
      For nested instances that expose their own TEXT properties, call `setProperties()` on the nested instance:
      
      ```javascript
      const nestedHeading = instance.findOne(n => n.type === "INSTANCE" && n.name === "Text Heading");
      if (nestedHeading) {
        nestedHeading.setProperties({ "Text#2104:5": "Actual heading text" });
      }
      ```
      
      **Step 3: Only fall back to direct node.characters for unmanaged text.** If text is NOT controlled by any component property, find text nodes directly. **Always load the node's actual font first** — instance text nodes inherit fonts from the source component, so don't assume Inter Regular:
      
      ```javascript
      const textNodes = instance.findAll(n => n.type === "TEXT");
      for (const t of textNodes) {
        await figma.loadFontAsync(t.fontName);
        t.characters = "Updated text";
      }
      ```
      
      ### detachInstance() invalidates ancestor node IDs
      
      **Warning:** When `detachInstance()` is called on a nested instance inside a library component instance, the parent instance may also get implicitly detached (converted from INSTANCE to FRAME with a **new ID**). Subsequent `getNodeByIdAsync(oldParentId)` returns null.
      
      ```javascript
      // WRONG — cached parent ID becomes invalid after child detach
      const parentId = parentInstance.id;
      nestedChild.detachInstance();
      const parent = await figma.getNodeByIdAsync(parentId); // null!
      
      // CORRECT — re-discover nodes by traversal from a stable (non-instance) parent
      const stableFrame = await figma.getNodeByIdAsync(manualFrameId); // a frame YOU created
      nestedChild.detachInstance();
      // Re-find the parent by traversing from the stable frame
      const parent = stableFrame.findOne(n => n.name === "ParentName");
      ```
      
      If you must detach multiple nested instances across sibling components, do it in a **single** `use_figma` call — discover all targets by traversal at the start before any detachment mutates the tree.
      
      ## Inspecting Component Metadata (Deep Traversal)
      
      These helpers extract the full property schema and descendant structure of a component. Useful for understanding complex components before creating instances or setting properties.
      
      ```javascript
      /**
       * Imports a component or component set from a library by its published key.
       * Tries COMPONENT first, then falls back to COMPONENT_SET.
       *
       * @param {string} componentKey - The published key of the component or component set.
       * @returns {Promise<ComponentNode|ComponentSetNode>}
       */
      async function importComponentByKey(componentKey) {
        try {
          return await figma.importComponentByKeyAsync(componentKey);
        } catch {
          try {
            return await figma.importComponentSetByKeyAsync(componentKey);
          } catch {
            throw new Error(`No Component or Component Set available with key '${componentKey}'`);
          }
        }
      }
      
      /**
       * Given a main component node, returns the component set parent if one exists,
       * otherwise returns the component itself. Used to get the top-level node that
       * holds `componentPropertyDefinitions`.
       *
       * @param {ComponentNode} mainComponent
       * @returns {ComponentNode|ComponentSetNode}
       */
      function getRelevantComponentNode(mainComponent) {
        return mainComponent.parent.type === "COMPONENT_SET"
          ? mainComponent.parent
          : mainComponent;
      }
      
      /**
       * Extracts `componentPropertyDefinitions` from a component or component set node
       * into a flat map keyed by property key.
       *
       * @param {ComponentNode|ComponentSetNode} node
       * @returns {Record<string, {name: string, type: string, key: string, variantOptions?: string[]}>}
       */
      function getComponentProps(node) {
        const result = {};
        for (let key in node.componentPropertyDefinitions) {
          const prop = {
            name: key.replace(/#[^#]+$/, ""),
            type: node.componentPropertyDefinitions[key].type,
            key: key
          };
          if (prop.type === "VARIANT") {
            prop.variantOptions = node.componentPropertyDefinitions[key].variantOptions;
          }
          result[key] = prop;
        }
        return result;
      }
      
      /**
       * Recursively walks a component tree and collects all INSTANCE and TEXT nodes
       * into `result`, keyed by `TYPE[name]`. Handles variant namespacing and
       * deduplicates nodes with identical names but differing property references.
       *
       * @param {SceneNode} node - The node to traverse.
       * @param {string[]} namespace - Accumulated variant names for the current path.
       * @param {Record<string, object>} result - Accumulator object populated in place.
       */
      function collectDescendants(node, namespace, result) {
        if (node.type === "INSTANCE" || node.type === "TEXT") {
          const references = node.componentPropertyReferences || {};
          if (!node.visible && !references.visible) return;
      
          const object = { type: node.type, name: node.name, references };
          let key = `${node.type}[${node.name}]`;
      
          if (result[key] && JSON.stringify(references) !== JSON.stringify(result[key].references)) {
            key += btoa(btoa(unescape(encodeURIComponent(JSON.stringify(references)))));
          }
      
          if (node.type === "INSTANCE") {
            const mainComponent = getRelevantComponentNode(node.mainComponent);
            object.properties = getComponentProps(mainComponent);
            object.descendants = {};
            object.mainComponentName = mainComponent.name;
            collectDescendants(mainComponent, [], object.descendants);
          }
      
          const start = namespace.length ? { variants: [] } : {};
          result[key] = Object.assign(object, result[key] || start);
          if (namespace.length) result[key].variants.push(namespace[namespace.length - 1]);
        } else if ("children" in node && node.visible) {
          if (node.type === "COMPONENT" && node.parent.type === "COMPONENT_SET") namespace.push(node.name);
          node.children.forEach(child => collectDescendants(child, namespace, result));
        }
      }
      
      /**
       * Returns structured metadata for a component or component set defined in the current file.
       *
       * @param {string} componentId - The node ID of a COMPONENT or COMPONENT_SET node.
       * @returns {Promise<{name: string, nodeId: string, properties: object, descendants: object}|undefined>}
       */
      async function getLocalComponentMetadata(componentId) {
        const node = await figma.getNodeByIdAsync(componentId);
        if (node.type === "COMPONENT_SET" || node.type === "COMPONENT") {
          const result = {
            name: node.name,
            nodeId: node.id,
            properties: {},
            descendants: {}
          };
          result.properties = getComponentProps(node);
          collectDescendants(node, [], result.descendants);
          return result;
        } else {
          throw new Error("Node is not a Component or Component Set");
        }
      }
      
      /**
       * Returns structured metadata for a published component or component set loaded by its key.
       *
       * @param {string} componentKey - The published key of the component or component set.
       * @returns {Promise<{name: string, nodeId: string, properties: object, descendants: object}>}
       */
      async function getPublishedComponentMetadata(componentKey) {
        const node = await importComponentByKey(componentKey);
        const result = {
          name: node.name,
          nodeId: node.id,
          properties: {},
          descendants: {}
        };
        result.properties = getComponentProps(node);
        collectDescendants(node, [], result.descendants);
        return result;
      }
      ```
      
      ### Full metadata extraction script
      
      ```javascript
      (async () => {
        try {
          // For local components, use getLocalComponentMetadata:
          const result = await getLocalComponentMetadata('COMPONENT_OR_SET_ID');
          figma.closePlugin(JSON.stringify(result));
      
          // For published components, use getPublishedComponentMetadata:
          // const result = await getPublishedComponentMetadata('COMPONENT_KEY');
          // figma.closePlugin(JSON.stringify(result));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
    • effect-style-patterns.md 3 KB
      # Effect Style API Patterns
      
      > Part of the [use_figma skill](../SKILL.md). How to create, apply, and inspect effect styles using the Plugin API.
      >
      > For design system context (effect types, variable bindings on effects, gotchas), see [wwds-effect-styles](working-with-design-systems/wwds-effect-styles.md).
      
      ## Contents
      
      - Listing Effect Styles
      - Creating a Drop Shadow Style
      - Applying Effect Styles to Nodes
      
      ## Listing Effect Styles
      
      ```javascript
      /**
       * Lists all local effect styles.
       *
       * @returns {Promise<Array<{id: string, name: string, key: string, effectCount: number}>>}
       */
      async function listEffectStyles() {
        const styles = await figma.getLocalEffectStylesAsync();
        return styles.map(s => ({
          id: s.id,
          name: s.name,
          key: s.key,
          effectCount: s.effects.length
        }));
      }
      ```
      
      Full runnable script:
      
      ```javascript
      (async () => {
        try {
          const results = await listEffectStyles();
          figma.closePlugin(JSON.stringify(results));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ## Creating a Drop Shadow Style
      
      Colors are **RGBA 0–1 range**. `effects` is a read-only array — always reassign, never mutate in place.
      
      ```javascript
      /**
       * Creates a drop shadow effect style.
       *
       * @param {string} name - e.g. "Elevation/200"
       * @param {{ r: number, g: number, b: number, a: number }} color - RGBA, 0-1 range
       * @param {{ x: number, y: number }} offset
       * @param {number} radius - blur radius
       * @param {number} [spread=0]
       * @returns {EffectStyle}
       */
      function createDropShadowStyle(name, color, offset, radius, spread) {
        const style = figma.createEffectStyle();
        style.name = name;
        style.effects = [{
          type: "DROP_SHADOW",
          color,
          offset,
          radius,
          spread: spread || 0,
          visible: true,
          blendMode: "NORMAL"
        }];
        return style;
      }
      ```
      
      Full runnable script:
      
      ```javascript
      (async () => {
        try {
          const style = createDropShadowStyle(
            "Elevation/200",
            { r: 0, g: 0, b: 0, a: 0.15 },
            { x: 0, y: 4 },
            12,
            0
          );
          figma.closePlugin(JSON.stringify({ id: style.id, name: style.name }));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ## Applying Effect Styles to Nodes
      
      ```javascript
      /**
       * Applies an effect style to all nodes on the current page that match a given name pattern.
       *
       * @param {string} styleId - The ID of an EffectStyle.
       * @param {string} nodeNamePattern - Substring match against node names.
       * @returns {number} - Number of nodes the style was applied to.
       */
      function applyEffectStyleToMatchingNodes(styleId, nodeNamePattern) {
        const nodes = figma.currentPage.findAll(n => n.name.includes(nodeNamePattern));
        let applied = 0;
        for (const node of nodes) {
          if ('effectStyleId' in node) {
            node.effectStyleId = styleId;
            applied++;
          }
        }
        return applied;
      }
      ```
      
      Full runnable script:
      
      ```javascript
      (async () => {
        try {
          const applied = applyEffectStyleToMatchingNodes('STYLE_ID', 'Card');
          figma.closePlugin(JSON.stringify({ applied }));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
    • gotchas.md 23 KB
      # Gotchas & Common Mistakes
      
      > Part of the [use_figma skill](../SKILL.md). Every known pitfall with WRONG/CORRECT code examples.
      
      ## Contents
      
      - Component properties and variant creation pitfalls
      - Paint, color, and variable binding pitfalls
      - Page context and plugin lifecycle pitfalls
      - Auto Layout and sizing order pitfalls (including HUG/FILL interactions)
      - Variant layout and geometry pitfalls
      - Variable scopes and mode pitfalls
      - Node cleanup and empty-fill pitfalls
      - detachInstance() and node ID invalidation
      
      
      ## New nodes default to (0,0) and overlap existing content
      
      Every `figma.create*()` call places the node at position (0,0). If you append multiple nodes directly to the page, they all stack on top of each other and on top of any existing content.
      
      **This only matters for nodes appended directly to the page** (i.e., top-level nodes). Nodes appended as children of other frames, components, or auto-layout containers are positioned by their parent — don't scan for overlaps when nesting nodes.
      
      ```js
      // WRONG — top-level node lands at (0,0), overlapping existing page content
      const frame = figma.createFrame()
      frame.name = "My New Frame"
      frame.resize(400, 300)
      figma.currentPage.appendChild(frame)
      
      // CORRECT — find existing content bounds and place the new top-level node to the right
      const page = figma.currentPage
      let maxX = 0
      for (const child of page.children) {
        const right = child.x + child.width
        if (right > maxX) maxX = right
      }
      const frame = figma.createFrame()
      frame.name = "My New Frame"
      frame.resize(400, 300)
      figma.currentPage.appendChild(frame)
      frame.x = maxX + 100  // 100px gap from rightmost existing content
      frame.y = 0
      
      // NOT NEEDED — child nodes inside a parent don't need overlap scanning
      const card = figma.createFrame()
      card.layoutMode = 'VERTICAL'
      const label = figma.createText()
      card.appendChild(label)  // positioned by auto-layout, no x/y needed
      ```
      
      ## `addComponentProperty` returns a string key, not an object — never hardcode or guess it
      
      Figma generates the property key dynamically (e.g. `"label#4:0"`). The suffix is unpredictable. Always capture and use the return value directly.
      
      ```js
      // WRONG — guessing / hardcoding the key
      comp.addComponentProperty('label', 'TEXT', 'Button')
      labelNode.componentPropertyReferences = { characters: 'label#0:1' }  // Error: key not found
      
      // WRONG — treating the return value as an object
      const result = comp.addComponentProperty('Label', 'TEXT', 'Button')
      const propKey = Object.keys(result)[0]  // BUG: returns '0' (first char index of string!)
      labelNode.componentPropertyReferences = { characters: propKey }  // Error: property '0' not found
      
      // CORRECT — the return value IS the key string, use it directly
      const propKey = comp.addComponentProperty('Label', 'TEXT', 'Button')
      // propKey === "label#4:0" (exact value varies; never assume it)
      labelNode.componentPropertyReferences = { characters: propKey }
      ```
      
      The same applies to `COMPONENT_SET` nodes — `addComponentProperty` always returns the property key as a string.
      
      ## MUST return ALL created/mutated node IDs
      
      Every script that creates or mutates nodes on the canvas must track and return all affected node IDs in the `figma.closePlugin()` response. Without these IDs, subsequent calls cannot reference, validate, or clean up those nodes.
      
      ```js
      // WRONG — only returns the parent frame ID, loses track of children
      const frame = figma.createFrame()
      const rect = figma.createRectangle()
      const text = figma.createText()
      frame.appendChild(rect)
      frame.appendChild(text)
      figma.closePlugin(JSON.stringify({ nodeId: frame.id }))
      
      // CORRECT — returns all created node IDs in a structured response
      const frame = figma.createFrame()
      const rect = figma.createRectangle()
      const text = figma.createText()
      frame.appendChild(rect)
      frame.appendChild(text)
      figma.closePlugin(JSON.stringify({
        createdNodeIds: [frame.id, rect.id, text.id],
        rootNodeId: frame.id
      }))
      
      // CORRECT — when mutating existing nodes, return those IDs too
      const nodes = figma.currentPage.findAll(n => n.name === 'Card')
      for (const n of nodes) {
        n.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
      }
      figma.closePlugin(JSON.stringify({
        mutatedNodeIds: nodes.map(n => n.id),
        count: nodes.length
      }))
      ```
      
      ## Colors are 0–1 range
      
      ```js
      // WRONG — will throw validation error (ZeroToOne enforced)
      node.fills = [{ type: 'SOLID', color: { r: 255, g: 0, b: 0 } }]
      
      // CORRECT
      node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
      ```
      
      ## Fills/strokes are immutable arrays
      
      ```js
      // WRONG — modifying in place does nothing
      node.fills[0].color = { r: 1, g: 0, b: 0 }
      
      // CORRECT — clone, modify, reassign
      const fills = JSON.parse(JSON.stringify(node.fills))
      fills[0].color = { r: 1, g: 0, b: 0 }
      node.fills = fills
      ```
      
      ## setBoundVariableForPaint returns a NEW paint
      
      ```js
      // WRONG — ignoring return value
      figma.variables.setBoundVariableForPaint(paint, "color", colorVar)
      node.fills = [paint]  // paint is unchanged!
      
      // CORRECT — capture the returned new paint
      const boundPaint = figma.variables.setBoundVariableForPaint(paint, "color", colorVar)
      node.fills = [boundPaint]
      ```
      
      ## Variable collection starts with 1 mode
      
      ```js
      // A new collection already has one mode — rename it, don't try to add first
      const collection = figma.variables.createVariableCollection("Colors")
      // collection.modes = [{ modeId: "...", name: "Mode 1" }]
      collection.renameMode(collection.modes[0].modeId, "Light")
      const darkModeId = collection.addMode("Dark")
      ```
      
      ## combineAsVariants requires ComponentNodes
      
      ```js
      // WRONG — passing frames
      const f1 = figma.createFrame()
      figma.combineAsVariants([f1], figma.currentPage) // Error!
      
      // CORRECT — passing components
      const c1 = figma.createComponent()
      c1.name = "variant=primary, size=md"
      const c2 = figma.createComponent()
      c2.name = "variant=secondary, size=md"
      figma.combineAsVariants([c1, c2], figma.currentPage)
      ```
      
      ## Page switching: sync setter throws
      
      The sync setter `figma.currentPage = page` **throws an error** in `use_figma` runtimes (MCP, evals, assistant). Use `await figma.setCurrentPageAsync(page)` instead — it switches the page and loads its content.
      
      ```js
      // WRONG — throws "Setting figma.currentPage is not supported in this runtime"
      figma.currentPage = targetPage
      
      // CORRECT — async method switches and loads content
      await figma.setCurrentPageAsync(targetPage)
      ```
      
      ## `get_metadata` only sees one page — use `use_figma` to discover all pages
      
      A Figma file can have multiple pages (canvas nodes). `get_metadata` operates on a single node/page — it cannot scan the entire document. To discover all pages and their top-level contents, use `use_figma`:
      
      ```js
      // WRONG — calling get_metadata with the file root or expecting it to list all pages
      // get_metadata only returns the subtree of the node you pass it
      
      // CORRECT — use use_figma to list pages, then inspect each one
      const pages = figma.root.children.map(p => `${p.name} id=${p.id} children=${p.children.length}`);
      figma.closePlugin(pages.join('\n'));
      ```
      
      Icons, variables, and components may live on pages other than the first. Always enumerate all pages before concluding that the file has no existing assets.
      
      ## Never use figma.notify()
      
      ```js
      // WRONG — throws "not implemented" error
      figma.notify("Done!")
      
      // CORRECT — use closePlugin for messaging
      figma.closePlugin("Done!")
      ```
      
      ## Script must always terminate
      
      ```js
      // WRONG — no closePlugin call, script hangs
      (async () => {
        figma.createRectangle()
      })()
      
      // CORRECT — always close
      (async () => {
        try {
          figma.createRectangle()
          figma.closePlugin("created")
        } catch(e) {
          figma.closePluginWithFailure(e.toString())
        }
      })()
      ```
      
      ## setBoundVariable for paint fields only works on SOLID paints
      
      ```js
      // Only SOLID paint type supports color variable binding
      // Gradient paints, image paints, etc. will throw
      const solidPaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }
      const bound = figma.variables.setBoundVariableForPaint(solidPaint, "color", colorVar)
      ```
      
      ## Explicit variable modes must be set per component
      
      ```js
      // WRONG — all variants render with the default (first) mode
      const colorCollection = figma.variables.createVariableCollection("Colors")
      // ... create variables and modes ...
      // Components all show the first mode's values by default!
      
      // CORRECT — set explicit mode on each component to get variant-specific values
      component.setExplicitVariableModeForCollection(colorCollection.id, targetModeId)
      ```
      
      ## `TextStyle.setBoundVariable` is not available in headless use_figma
      
      `setBoundVariable` exists on `TextStyle` in the typed API but is **not available** when running scripts through `use_figma` (MCP, headless assistant mode). Calling it will throw `"not a function"`.
      
      ```js
      // WRONG — throws "not a function" in use_figma / headless
      const ts = figma.createTextStyle()
      ts.setBoundVariable("fontSize", fontSizeVar)
      
      // CORRECT (headless) — set raw values; bind variables interactively in Figma later
      const ts = figma.createTextStyle()
      ts.fontSize = 24
      ```
      
      This only affects `TextStyle`. Variable binding on **nodes** (`node.setBoundVariable(...)`) and on **paint objects** (`figma.variables.setBoundVariableForPaint(...)`) still works in headless mode as expected.
      
      If live variable binding on text styles is required, create the styles with raw values via `use_figma`, then bind variables interactively through the Figma Styles panel or a full interactive plugin.
      
      ## `lineHeight` and `letterSpacing` must be objects, not bare numbers
      
      ```js
      // WRONG — throws or silently does nothing
      style.lineHeight = 1.5
      style.lineHeight = 24
      style.letterSpacing = 0
      
      // CORRECT
      style.lineHeight = { unit: "AUTO" }                    // auto/intrinsic
      style.lineHeight = { value: 24, unit: "PIXELS" }       // fixed pixel height
      style.lineHeight = { value: 150, unit: "PERCENT" }     // percentage of font size
      
      style.letterSpacing = { value: 0, unit: "PIXELS" }     // no tracking
      style.letterSpacing = { value: -0.5, unit: "PIXELS" }  // tight
      style.letterSpacing = { value: 5, unit: "PERCENT" }    // percent-based
      ```
      
      This applies to both `TextStyle` and `TextNode` properties. The same rule applies inside `use_figma`, interactive plugins, and any other plugin API context.
      
      ## Font style names are file-dependent — probe before assuming
      
      Font style names vary per provider and per Figma file. `"SemiBold"` and `"Semi Bold"` are different strings. Loading a font with the wrong style string **throws silently or errors** — there is no canonical list.
      
      ```js
      // WRONG — guessing style names
      await figma.loadFontAsync({ family: "Inter", style: "SemiBold" }) // may throw
      
      // CORRECT — probe which style names are available
      const candidates = ["SemiBold", "Semi Bold", "Semibold"]
      for (const style of candidates) {
        try {
          await figma.loadFontAsync({ family: "Inter", style })
          // capture the one that works
          break
        } catch (_) {}
      }
      ```
      
      When building a type ramp script, always verify font styles against the target file before hardcoding them.
      
      ## combineAsVariants does NOT auto-layout in headless mode
      
      ```js
      // WRONG — all variants stack at position (0, 0), resulting in a tiny ComponentSet
      const components = [comp1, comp2, comp3]
      const cs = figma.combineAsVariants(components, figma.currentPage)
      // cs.width/height will be the size of a SINGLE variant!
      
      // CORRECT — manually layout children in a grid after combining
      const cs = figma.combineAsVariants(components, figma.currentPage)
      const colWidth = 120
      const rowHeight = 56
      cs.children.forEach((child, i) => {
        const col = i % numCols
        const row = Math.floor(i / numCols)
        child.x = col * colWidth
        child.y = row * rowHeight
      })
      // CRITICAL: resize from actual child bounds, not formula — formula errors leave variants outside the boundary
      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)
      ```
      
      ## COLOR variable values use {r, g, b, a} (with alpha)
      
      ```js
      // Paint colors use {r, g, b} (no alpha — opacity is a separate paint property)
      node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
      
      // But COLOR variable values use {r, g, b, a} — alpha maps to paint opacity
      const colorVar = figma.variables.createVariable("bg", collection, "COLOR")
      colorVar.setValueForMode(modeId, { r: 1, g: 0, b: 0, a: 1 })  // opaque red
      colorVar.setValueForMode(modeId, { r: 0, g: 0, b: 0, a: 0 })  // fully transparent
      
      // ⚠️ Don't confuse: {r, g, b} for paint colors vs {r, g, b, a} for variable values
      ```
      
      ## `layoutSizingVertical`/`layoutSizingHorizontal` = `'FILL'` requires auto-layout parent FIRST
      
      ```js
      // WRONG — setting FILL before the node is a child of an auto-layout frame
      const child = figma.createFrame()
      child.layoutSizingVertical = 'FILL'  // ERROR: "FILL can only be set on children of auto-layout frames"
      parent.appendChild(child)
      
      // CORRECT — append to auto-layout parent FIRST, then set FILL
      const child = figma.createFrame()
      parent.appendChild(child)            // parent must have layoutMode set
      child.layoutSizingVertical = 'FILL'  // Works!
      ```
      
      ## HUG parents collapse FILL children
      
      A `HUG` parent cannot give `FILL` children meaningful size. If children have `layoutSizingHorizontal = "FILL"` but the parent is `"HUG"`, the children collapse to minimum size. The parent must be `"FILL"` or `"FIXED"` for FILL children to expand. This is a common cause of truncated text in select fields, inputs, and action rows.
      
      ```js
      // WRONG — parent hugs, so FILL children get zero extra space
      const parent = figma.createFrame()
      parent.layoutMode = 'HORIZONTAL'
      parent.layoutSizingHorizontal = 'HUG'
      const child = figma.createFrame()
      parent.appendChild(child)
      child.layoutSizingHorizontal = 'FILL'  // collapses to min size!
      
      // CORRECT — parent must be FIXED or FILL for FILL children to expand
      const parent = figma.createFrame()
      parent.layoutMode = 'HORIZONTAL'
      parent.resize(400, 50)
      parent.layoutSizingHorizontal = 'FIXED'  // or 'FILL' if inside another auto-layout
      const child = figma.createFrame()
      parent.appendChild(child)
      child.layoutSizingHorizontal = 'FILL'  // expands to fill remaining 400px
      ```
      
      ## `layoutGrow` with a hugging parent causes content compression
      
      ```js
      // WRONG — layoutGrow on a child when parent has primaryAxisSizingMode='AUTO' (hug)
      // causes the child to SHRINK below its natural size instead of expanding
      const parent = figma.createComponent()
      parent.layoutMode = 'VERTICAL'
      parent.primaryAxisSizingMode = 'AUTO'  // hug contents
      const content = figma.createFrame()
      content.layoutMode = 'VERTICAL'
      content.primaryAxisSizingMode = 'AUTO'
      parent.appendChild(content)
      content.layoutGrow = 1  // BUG: content compresses, children hidden!
      
      // CORRECT — only use layoutGrow when parent has FIXED sizing with extra space
      content.layoutGrow = 0  // let content take its natural size
      // OR: set parent to FIXED sizing first
      parent.primaryAxisSizingMode = 'FIXED'
      parent.resizeWithoutConstraints(300, 500)
      content.layoutGrow = 1  // NOW it correctly fills remaining space
      ```
      
      ## `resize()` resets `primaryAxisSizingMode` and `counterAxisSizingMode` to FIXED
      
      ```js
      // WRONG — resize() after setting sizing mode overwrites it back to FIXED
      const frame = figma.createComponent()
      frame.layoutMode = 'VERTICAL'
      frame.primaryAxisSizingMode = 'AUTO'  // hug height
      frame.counterAxisSizingMode = 'FIXED'
      frame.resize(300, 10)  // BUG: resets BOTH axes to 'FIXED'! Height stays at 10px forever.
      
      // CORRECT — call resize() FIRST, then set sizing modes
      const frame = figma.createComponent()
      frame.layoutMode = 'VERTICAL'
      frame.resize(300, 10)  // set initial dimensions first
      frame.counterAxisSizingMode = 'FIXED'  // keep width fixed at 300
      frame.primaryAxisSizingMode = 'AUTO'   // NOW set height to hug — this sticks!
      // Or use the modern shorthand (equivalent):
      // frame.layoutSizingHorizontal = 'FIXED'
      // frame.layoutSizingVertical = 'HUG'
      ```
      
      ## Node positions don't auto-reset after reparenting
      
      ```js
      // WRONG — assuming positions reset when moving a node into a new parent
      const node = figma.createRectangle()
      node.x = 500; node.y = 500;
      figma.currentPage.appendChild(node)
      section.appendChild(node)  // node still at (500, 500) relative to section!
      
      // CORRECT — explicitly set x/y after ANY reparenting operation
      section.appendChild(node)
      node.x = 80; node.y = 80;  // reset to desired position within section
      ```
      
      ## Grid layout with mixed-width rows causes overlaps
      
      ```js
      // WRONG — using a single column offset for rows with different-width items
      // e.g. vertical cards (320px) and horizontal cards (500px) in a 2-row grid
      for (let i = 0; i < allCards.length; i++) {
        allCards[i].x = (i % 4) * 370  // 370 works for 320px cards but NOT 500px cards!
      }
      
      // CORRECT — compute each row's spacing independently based on actual child widths
      const gap = 50
      let x = 0
      for (const card of horizontalCards) {
        card.x = x
        x += card.width + gap  // use actual width, not a fixed column size
      }
      ```
      
      ## Sections don't auto-resize to fit content
      
      ```js
      // WRONG — section stays at default size, content overflows
      const section = figma.createSection()
      section.name = "My Section"
      section.appendChild(someNode) // node may be outside section bounds
      
      // CORRECT — explicitly resize after adding content
      const section = figma.createSection()
      section.name = "My Section"
      section.appendChild(someNode)
      section.resizeWithoutConstraints(
        Math.max(someNode.width + 100, 800),
        Math.max(someNode.height + 100, 600)
      )
      ```
      
      ## `counterAxisAlignItems` does NOT support `'STRETCH'`
      
      ```js
      // WRONG — 'STRETCH' is not a valid enum value
      comp.counterAxisAlignItems = 'STRETCH'
      // Error: Invalid enum value. Expected 'MIN' | 'MAX' | 'CENTER' | 'BASELINE', received 'STRETCH'
      
      // CORRECT — use 'MIN' on the parent, then set children to FILL on the cross axis
      comp.counterAxisAlignItems = 'MIN'
      comp.appendChild(child)
      // For vertical layout, stretch width:
      child.layoutSizingHorizontal = 'FILL'
      // For horizontal layout, stretch height:
      child.layoutSizingVertical = 'FILL'
      ```
      
      ## Variable collection mode limits are plan-dependent
      
      ```js
      // Figma limits modes per collection based on the team/org plan:
      //   Free: 1 mode only (no addMode)
      //   Professional: up to 4 modes
      //   Organization/Enterprise: up to 40+ modes
      //
      // WRONG — creating 20 modes on a Professional plan will fail silently or throw
      const coll = figma.variables.createVariableCollection("Variants")
      for (let i = 0; i < 20; i++) coll.addMode("mode" + i) // May fail!
      
      // CORRECT — if you need many modes, split across multiple collections
      // E.g., instead of 1 collection with 20 modes (variant×color):
      //   Collection A: 4 modes (variant: plain/outlined/soft/solid)
      //   Collection B: 5 modes (color: neutral/primary/danger/success/warning)
      // Then use setExplicitVariableModeForCollection for BOTH on each component
      ```
      
      ## Variables default to `ALL_SCOPES` — always set scopes explicitly
      
      ```js
      // WRONG — variable appears in every property picker (fills, text, strokes, spacing, etc.)
      const bgColor = figma.variables.createVariable("Background/Default", coll, "COLOR")
      // bgColor.scopes defaults to ["ALL_SCOPES"] — pollutes all dropdowns
      
      // CORRECT — restrict to relevant property pickers
      const bgColor = figma.variables.createVariable("Background/Default", coll, "COLOR")
      bgColor.scopes = ["FRAME_FILL", "SHAPE_FILL", "EFFECT_COLOR"]  // fill pickers only
      
      const textColor = figma.variables.createVariable("Text/Default", coll, "COLOR")
      textColor.scopes = ["TEXT_FILL"]  // text color picker only
      
      const borderColor = figma.variables.createVariable("Border/Default", coll, "COLOR")
      borderColor.scopes = ["STROKE_COLOR"]  // stroke picker only
      
      const spacing = figma.variables.createVariable("Space/400", coll, "FLOAT")
      spacing.scopes = ["GAP"]  // gap/spacing pickers only
      
      // Hide primitives that are only referenced via aliases
      const primitive = figma.variables.createVariable("Brand/500", coll, "COLOR")
      primitive.scopes = []  // hidden from all pickers
      ```
      
      ## Binding fills on nodes with empty fills
      
      ```js
      // WRONG — binding to a node with no fills does nothing
      const comp = figma.createComponent()
      comp.fills = [] // transparent
      // Can't bind a color variable to fills that don't exist
      
      // CORRECT — add a placeholder SOLID fill, then bind the variable
      const comp = figma.createComponent()
      const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }
      const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", colorVar)
      comp.fills = [boundPaint]
      // The variable's resolved value (which may be transparent) will control the actual color
      ```
      
      ## Mode names must be descriptive — never leave 'Mode 1'
      
      Every new `VariableCollection` starts with one mode named `'Mode 1'`. Always rename it immediately. For single-mode collections use `'Default'`; for multi-mode collections use names from the source (e.g. `'Light'`/`'Dark'`, `'Desktop'`/`'Tablet'`/`'Mobile'`).
      
          // WRONG — generic names give no semantic meaning
          const coll = figma.variables.createVariableCollection('Colors')
          // coll.modes[0].name === 'Mode 1' — left as-is
          const darkId = coll.addMode('Mode 2')
      
          // CORRECT — rename immediately to match the source
          const coll = figma.variables.createVariableCollection('Colors')
          coll.renameMode(coll.modes[0].modeId, 'Light')   // was 'Mode 1'
          const darkId = coll.addMode('Dark')
      
          // For single-mode collections (primitives, spacing, etc.)
          const spacing = figma.variables.createVariableCollection('Spacing')
          spacing.renameMode(spacing.modes[0].modeId, 'Default')  // was 'Mode 1'
      
      ## CSS variable names must not contain spaces
      
      When constructing a `var(--name)` string from a Figma variable name, replace BOTH slashes AND spaces with hyphens and convert to lowercase.
      
          // WRONG — only replacing slashes leaves spaces like 'var(--color-bg-brand secondary hover)'
          v.setVariableCodeSyntax('WEB', `var(--${figmaName.replace(/\//g, '-').toLowerCase()})`)
      
          // CORRECT — replace all whitespace and slashes in one pass
          v.setVariableCodeSyntax('WEB', `var(--${figmaName.replace(/[\s\/]+/g, '-').toLowerCase()})`)
      
      **Best practice**: Preserve the original CSS variable name from the source token file rather than deriving it from the Figma name.
      
          // Preferred — use the source CSS name directly
          v.setVariableCodeSyntax('WEB', `var(${token.cssVar})`)  // e.g. '--color-bg-brand-secondary-hover'
      
      ## `detachInstance()` invalidates ancestor node IDs
      
      When `detachInstance()` is called on a nested instance inside a library component instance, the parent instance may also get implicitly detached (converted from INSTANCE to FRAME with a **new ID**). Any previously cached ID for the parent becomes invalid.
      
      ```js
      // WRONG — using cached parent ID after child detach
      const parentId = parentInstance.id;
      nestedChild.detachInstance();
      const parent = await figma.getNodeByIdAsync(parentId); // null! ID changed.
      
      // CORRECT — re-discover by traversal from a stable (non-instance) frame
      const stableFrame = await figma.getNodeByIdAsync(manualFrameId);
      nestedChild.detachInstance();
      const parent = stableFrame.findOne(n => n.name === "ParentName");
      ```
      
      If detaching multiple nested instances across siblings, do it in a **single** `use_figma` call — discover all targets by traversal before any detachment mutates the tree.
      
    • maintainers.yml 418 B
      api-reference.md: mcp_server
      common-patterns.md: mcp_server
      component-patterns.md: mcp_server
      effect-style-patterns.md: mcp_server
      gotchas.md: mcp_server
      plugin-api-patterns.md: mcp_server
      plugin-api-standalone.d.ts: mcp_server
      plugin-api-standalone.index.md: mcp_server
      text-style-patterns.md: mcp_server
      validation-and-recovery.md: mcp_server
      variable-patterns.md: mcp_server
      working-with-design-systems: mcp_server
      
    • plugin-api-patterns.md 12.1 KB
      # Plugin API Patterns
      
      > Part of the [use_figma skill](../SKILL.md). Quick reference for common Figma Plugin API operations.
      
      ## Contents
      
      - Execution Basics
      - Creating Nodes
      - Fills and Strokes
      - Auto Layout
      - Effects
      - Opacity and Blend Modes
      - Corner Radius and Clipping
      - Grouping and Organization
      - Components and Variants
      - Styles
      - Cloning, Finding Nodes, and Grids
      - Constraints and Viewport
      
      
      ## Execution Basics
      
      ### Page Context
      
      Page context resets between `use_figma` calls — `figma.currentPage` always starts on the first page. Use `await figma.setCurrentPageAsync(page)` at the start of each invocation to switch to the correct page.
      
      ```javascript
      const targetPage = figma.root.children.find(p => p.name === "My Page");
      await figma.setCurrentPageAsync(targetPage);
      // targetPage.children is now populated
      ```
      
      ### Closing the Plugin
      
      Every execution **must** call `figma.closePlugin()` on success and `figma.closePluginWithFailure()` on error:
      
      ```javascript
      figma.closePlugin("Success message describing what was done");
      figma.closePluginWithFailure("Description of what went wrong");
      ```
      
      `figma.notify()` does **not** exist. Return all information via the close message string.
      
      ### Working Incrementally
      
      Don't build an entire screen in one call. Break work into small steps:
      1. Create tokens/variables
      2. Create text styles
      3. Build individual components
      4. Compose sections
      5. Assemble screens
      
      Verify structure with `get_metadata` between steps. Use `get_screenshot` after each major creation milestone to catch visual problems early.
      
      ## Creating Nodes
      
      ### Frames
      
      ```javascript
      const frame = figma.createFrame();
      frame.name = "Container";
      frame.resize(1440, 900);
      frame.x = 0;
      frame.y = 0;
      frame.fills = [{ type: "SOLID", color: { r: 0.98, g: 0.98, b: 0.99 } }];
      ```
      
      ### Text
      
      ```javascript
      // MUST load font before any text operations
      await figma.loadFontAsync({ family: "Inter", style: "Regular" });
      
      const text = figma.createText();
      text.fontName = { family: "Inter", style: "Regular" };
      text.fontSize = 16;
      text.lineHeight = { value: 24, unit: "PIXELS" };
      text.letterSpacing = { value: 0, unit: "PERCENT" };
      text.characters = "Hello World";
      text.fills = [{ type: "SOLID", color: { r: 0.1, g: 0.1, b: 0.12 } }];
      ```
      
      ### Rectangles
      
      ```javascript
      const rect = figma.createRectangle();
      rect.name = "Background";
      rect.resize(400, 300);
      rect.cornerRadius = 12;
      rect.fills = [{ type: "SOLID", color: { r: 0.95, g: 0.95, b: 0.96 } }];
      ```
      
      ### Ellipses
      
      ```javascript
      const circle = figma.createEllipse();
      circle.name = "Avatar Circle";
      circle.resize(48, 48);
      circle.fills = [{ type: "SOLID", color: { r: 0.85, g: 0.87, b: 0.90 } }];
      ```
      
      ### Lines
      
      ```javascript
      const line = figma.createLine();
      line.name = "Divider";
      line.resize(400, 0);
      line.strokes = [{ type: "SOLID", color: { r: 0, g: 0, b: 0 }, opacity: 0.08 }];
      line.strokeWeight = 1;
      ```
      
      ### SVG Import
      
      ```javascript
      const svgString = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M5 12h14M12 5l7 7-7 7" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
      </svg>`;
      
      const node = figma.createNodeFromSvg(svgString);
      node.name = "Icon/Arrow Right";
      node.resize(24, 24);
      ```
      
      ## Fills & Strokes
      
      ### Solid Fill
      
      ```javascript
      node.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.2, b: 0.25 } }];
      ```
      
      ### Fill with Opacity
      
      ```javascript
      node.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.2, b: 0.25 }, opacity: 0.5 }];
      ```
      
      ### No Fill (Transparent)
      
      ```javascript
      node.fills = [];
      ```
      
      ### Linear Gradient
      
      ```javascript
      node.fills = [{
        type: "GRADIENT_LINEAR",
        gradientStops: [
          { color: { r: 0.2, g: 0.36, b: 0.96, a: 1 }, position: 0 },
          { color: { r: 0.56, g: 0.24, b: 0.88, a: 1 }, position: 1 }
        ],
        gradientTransform: [[1, 0, 0], [0, 1, 0]]
      }];
      ```
      
      ### Strokes
      
      ```javascript
      node.strokes = [{ type: "SOLID", color: { r: 0.85, g: 0.85, b: 0.87 } }];
      node.strokeWeight = 1;
      node.strokeAlign = "INSIDE";  // "CENTER", "OUTSIDE"
      ```
      
      ### Multiple Fills (Layered)
      
      ```javascript
      node.fills = [
        { type: "SOLID", color: { r: 0.95, g: 0.95, b: 0.96 } },
        { type: "SOLID", color: { r: 0.2, g: 0.36, b: 0.96 }, opacity: 0.05 }
      ];
      ```
      
      ## Auto Layout
      
      ### Setting Up Auto Layout
      
      ```javascript
      const frame = figma.createFrame();
      frame.layoutMode = "VERTICAL";              // or "HORIZONTAL"
      frame.primaryAxisSizingMode = "AUTO";       // Hug main axis
      frame.counterAxisSizingMode = "FIXED";      // Fixed cross axis
      frame.resize(360, 1);                        // Width fixed, height auto
      frame.itemSpacing = 16;                      // Gap between children
      frame.paddingTop = 24;
      frame.paddingBottom = 24;
      frame.paddingLeft = 24;
      frame.paddingRight = 24;
      ```
      
      ### Alignment
      
      ```javascript
      // Main axis (direction of layout)
      frame.primaryAxisAlignItems = "MIN";            // Start
      frame.primaryAxisAlignItems = "CENTER";         // Center
      frame.primaryAxisAlignItems = "MAX";            // End
      frame.primaryAxisAlignItems = "SPACE_BETWEEN";  // Distribute
      
      // Cross axis
      frame.counterAxisAlignItems = "MIN";     // Start
      frame.counterAxisAlignItems = "CENTER";  // Center
      frame.counterAxisAlignItems = "MAX";     // End
      // NOTE: 'STRETCH' is NOT valid — use 'MIN' + child.layoutSizingX = 'FILL'
      ```
      
      ### Child Sizing
      
      ```javascript
      // IMPORTANT: FILL can only be set AFTER the child is appended to an auto-layout parent
      parent.appendChild(child)
      child.layoutSizingHorizontal = "FILL";   // Stretch to parent
      child.layoutSizingHorizontal = "HUG";    // Shrink to content
      child.layoutSizingHorizontal = "FIXED";  // Manual width
      
      child.layoutSizingVertical = "FILL";
      child.layoutSizingVertical = "HUG";
      child.layoutSizingVertical = "FIXED";
      ```
      
      ### Wrapping (Grid-like Layout)
      
      ```javascript
      frame.layoutMode = "HORIZONTAL";
      frame.layoutWrap = "WRAP";
      frame.itemSpacing = 24;          // Horizontal gap
      frame.counterAxisSpacing = 24;   // Vertical gap (between rows)
      ```
      
      ### Absolute Positioning Within Auto Layout
      
      ```javascript
      child.layoutPositioning = "ABSOLUTE";
      child.constraints = { horizontal: "MAX", vertical: "MIN" };  // Top-right
      child.x = parentWidth - childWidth - 8;
      child.y = 8;
      ```
      
      ## Effects
      
      ### Drop Shadow
      
      ```javascript
      node.effects = [{
        type: "DROP_SHADOW",
        color: { r: 0, g: 0, b: 0, a: 0.08 },
        offset: { x: 0, y: 4 },
        radius: 16,
        spread: -2,
        visible: true,
        blendMode: "NORMAL"
      }];
      ```
      
      ### Inner Shadow
      
      ```javascript
      node.effects = [{
        type: "INNER_SHADOW",
        color: { r: 0, g: 0, b: 0, a: 0.05 },
        offset: { x: 0, y: 1 },
        radius: 2,
        spread: 0,
        visible: true,
        blendMode: "NORMAL"
      }];
      ```
      
      ### Background Blur
      
      ```javascript
      node.effects = [{
        type: "BACKGROUND_BLUR",
        radius: 16,
        visible: true
      }];
      ```
      
      ### Layer Blur
      
      ```javascript
      node.effects = [{
        type: "LAYER_BLUR",
        radius: 8,
        visible: true
      }];
      ```
      
      ### Multiple Effects
      
      ```javascript
      node.effects = [
        { type: "DROP_SHADOW", color: { r: 0, g: 0, b: 0, a: 0.04 }, offset: { x: 0, y: 1 }, radius: 3, spread: 0, visible: true, blendMode: "NORMAL" },
        { type: "DROP_SHADOW", color: { r: 0, g: 0, b: 0, a: 0.06 }, offset: { x: 0, y: 8 }, radius: 24, spread: -4, visible: true, blendMode: "NORMAL" }
      ];
      ```
      
      ## Opacity & Blend Modes
      
      ```javascript
      node.opacity = 0.5;
      node.blendMode = "NORMAL";    // "MULTIPLY", "SCREEN", "OVERLAY", "DARKEN", "LIGHTEN", etc.
      ```
      
      ## Corner Radius
      
      ```javascript
      // Uniform
      node.cornerRadius = 12;
      
      // Per-corner
      node.topLeftRadius = 12;
      node.topRightRadius = 12;
      node.bottomLeftRadius = 0;
      node.bottomRightRadius = 0;
      ```
      
      ## Clipping
      
      ```javascript
      frame.clipsContent = true;   // Children clipped to frame bounds
      ```
      
      ## Grouping & Organization
      
      ### Groups
      
      ```javascript
      const group = figma.group([node1, node2, node3], figma.currentPage);
      group.name = "Grouped Elements";
      ```
      
      ### Sections
      
      ```javascript
      const section = figma.createSection();
      section.name = "My Section";
      section.resizeWithoutConstraints(800, 600);
      section.x = 0;
      section.y = 0;
      // IMPORTANT: Sections don't auto-resize — always resize after adding content
      ```
      
      ### Appending Children
      
      ```javascript
      parentFrame.appendChild(childNode);
      
      // Insert at a specific index
      parentFrame.insertChild(0, childNode);  // Insert at beginning
      ```
      
      ## Components & Variants
      
      ### Create Component
      
      ```javascript
      const component = figma.createComponent();
      component.name = "Button/Primary";
      component.description = "Primary action button.";
      ```
      
      ### Create Instance
      
      ```javascript
      const instance = component.createInstance();
      instance.x = 200;
      instance.y = 100;
      ```
      
      ### Import Components by Key (Team Libraries)
      
      These methods import components from **team libraries** (not the same file). For components in the current file, use `figma.getNodeByIdAsync()` or `findOne()`/`findAll()`.
      
      ```javascript
      // Import a published component from a team library by its key
      const comp = await figma.importComponentByKeyAsync(componentKey)
      const instance = comp.createInstance()
      
      // Import a published component set from a team library by its key
      const set = await figma.importComponentSetByKeyAsync(componentSetKey)
      const variant = set.defaultVariant
      const variantInstance = variant.createInstance()
      ```
      
      ### Combine as Variants
      
      ```javascript
      // IMPORTANT: Pass ComponentNodes (not frames)
      const componentSet = figma.combineAsVariants(
        [variantA, variantB, variantC],
        figma.currentPage
      );
      componentSet.name = "Button";
      componentSet.description = "Button component with multiple variants.";
      
      // CRITICAL: Layout variants in a grid after combining (they stack at 0,0)
      let maxX = 0, maxY = 0;
      componentSet.children.forEach((child, i) => {
        child.x = (i % numCols) * colWidth;
        child.y = Math.floor(i / numCols) * rowHeight;
      });
      for (const child of componentSet.children) {
        maxX = Math.max(maxX, child.x + child.width);
        maxY = Math.max(maxY, child.y + child.height);
      }
      componentSet.resizeWithoutConstraints(maxX + 40, maxY + 40);
      ```
      
      ### Component Properties
      
      ```javascript
      // addComponentProperty returns a STRING key — capture it!
      const labelKey = component.addComponentProperty("label", "TEXT", "Button");
      const showIconKey = component.addComponentProperty("showIcon", "BOOLEAN", true);
      const iconSlotKey = component.addComponentProperty("iconSlot", "INSTANCE_SWAP", defaultIconId);
      
      // MUST link properties to child nodes via componentPropertyReferences
      labelNode.componentPropertyReferences = { characters: labelKey };
      iconInstance.componentPropertyReferences = {
        visible: showIconKey,
        mainComponent: iconSlotKey
      };
      ```
      
      ## Styles
      
      ### Text Style
      
      ```javascript
      await figma.loadFontAsync({ family: "Inter", style: "Regular" });
      
      const style = figma.createTextStyle();
      style.name = "Body/Default";
      style.fontName = { family: "Inter", style: "Regular" };
      style.fontSize = 16;
      style.lineHeight = { value: 24, unit: "PIXELS" };
      style.letterSpacing = { value: 0, unit: "PERCENT" };
      
      // Apply to a text node
      textNode.textStyleId = style.id;
      ```
      
      ### Effect Style
      
      ```javascript
      const shadowStyle = figma.createEffectStyle();
      shadowStyle.name = "Shadow/Subtle";
      shadowStyle.effects = [{
        type: "DROP_SHADOW",
        color: { r: 0, g: 0, b: 0, a: 0.06 },
        offset: { x: 0, y: 2 },
        radius: 8,
        spread: 0,
        visible: true,
        blendMode: "NORMAL"
      }];
      
      // Apply to a node
      frame.effectStyleId = shadowStyle.id;
      ```
      
      ## Cloning & Duplication
      
      ```javascript
      const clone = originalNode.clone();
      clone.x = originalNode.x + originalNode.width + 40;
      clone.name = "Copy of " + originalNode.name;
      ```
      
      ## Finding Nodes
      
      ```javascript
      // Find by name on current page
      const node = figma.currentPage.findOne(n => n.name === "My Frame");
      
      // Find all by type
      const allTexts = figma.currentPage.findAll(n => n.type === "TEXT");
      
      // Find all by name pattern
      const allButtons = figma.currentPage.findAll(n => n.name.startsWith("Button/"));
      ```
      
      ## Layout Grids
      
      ```javascript
      frame.layoutGrids = [
        {
          pattern: "COLUMNS",
          alignment: "STRETCH",
          count: 12,
          gutterSize: 24,
          offset: 80,
          visible: true
        }
      ];
      ```
      
      ## Constraints (Non-Auto-Layout Frames)
      
      ```javascript
      child.constraints = {
        horizontal: "LEFT_RIGHT",  // LEFT, RIGHT, CENTER, LEFT_RIGHT, SCALE
        vertical: "TOP"            // TOP, BOTTOM, CENTER, TOP_BOTTOM, SCALE
      };
      ```
      
      ## Viewport & Zoom
      
      ```javascript
      // Zoom to fit specific nodes
      figma.viewport.scrollAndZoomIntoView([frame1, frame2]);
      ```
      
    • plugin-api-standalone.d.ts 440.2 KB
      // https://raw.githubusercontent.com/figma/plugin-typings/refs/heads/master/plugin-api-standalone.d.ts
      
      /* plugin-typings are auto-generated. Do not update them directly. See developer-docs/ for instructions. */
      /**
       * NOTE: This file is useful if you want to import specific types eg.
       * import type { SceneNode } from "@figma/plugin-typings/plugin-api-standalone"
       */
      /**
       * @see https://developers.figma.com/docs/plugins/api/properties/figma-on
       */
      declare type ArgFreeEventType =
        | 'selectionchange'
        | 'currentpagechange'
        | 'close'
        | 'timerstart'
        | 'timerstop'
        | 'timerpause'
        | 'timerresume'
        | 'timeradjust'
        | 'timerdone'
      /**
       * @see https://developers.figma.com/docs/plugins/api/figma
       */
      interface PluginAPI {
        /**
         * The version of the Figma API this plugin is running on, as defined in your `manifest.json` in the `"api"` field.
         */
        readonly apiVersion: '1.0.0'
        /**
         * The currently executing command from the `manifest.json` file. It is the command string in the `ManifestMenuItem` (more details in the [manifest guide](https://developers.figma.com/docs/plugins/manifest)). If the plugin does not have any menu item, this property is undefined.
         */
        readonly command: string
        /**
         * The current editor type this plugin is running in. See also [Setting editor type](https://developers.figma.com/docs/plugins/setting-editor-type).
         */
        readonly editorType: 'figma' | 'figjam' | 'dev' | 'slides' | 'buzz'
        /**
         * Return the context the plugin is current running in.
         *
         * - `default` - The plugin is running as a normal plugin.
         * - `textreview` - The plugin is running to provide text review functionality.
         * - `inspect` - The plugin is running in the Inspect panel in Dev Mode.
         * - `codegen` - The plugin is running in the Code section of the Inspect panel in Dev Mode.
         * - `linkpreview` - The plugin is generating a link preview for a [Dev resource](https://help.figma.com/hc/en-us/articles/15023124644247#Add_external_links_and_resources_for_developers) in Dev Mode.
         * - `auth` - The plugin is running to authenticate a user in Dev Mode.
         *
         * Caution: The `linkpreview` and `auth` modes are only available to partner and Figma-owned plugins.
         *
         * @remarks
         * Here’s a simplified example where you can create an if statement in a plugin that has one set of functionality when it is run in `Dev Mode`, and another set of functionality when run in Figma design:
         * ```ts title="Code sample to determine editorType and mode"
         * if (figma.editorType === "dev") {
         *   // Read the document and listen to API events
         *   if (figma.mode === "inspect") {
         *     // Running in inspect panel mode
         *   } else if (figma.mode === "codegen") {
         *     // Running in codegen mode
         *   }
         * } else if (figma.editorType === "figma") {
         *   // If the plugin is run in Figma design, edit the document
         *   if (figma.mode === 'textreview') {
         *     // Running in text review mode
         *   }
         * } else if (figma.editorType === "figjam") {
         *   // Do FigJam only operations
         *   if (figma.mode === 'textreview') {
         *     // Running in text review mode
         *   }
         * }
         * ```
         */
        readonly mode: 'default' | 'textreview' | 'inspect' | 'codegen' | 'linkpreview' | 'auth'
        /**
         * The value specified in the `manifest.json` "id" field. This only exists for Plugins.
         */
        readonly pluginId?: string
        /**
         * Similar to `figma.pluginId` but for widgets. The value specified in the `manifest.json` "id" field. This only exists for Widgets.
         */
        readonly widgetId?: string
        /**
         * The file key of the current file this plugin is running on.
         * **Only [private plugins](https://help.figma.com/hc/en-us/articles/4404228629655-Create-private-organization-plugins) and Figma-owned resources (such as the Jira and Asana widgets) have access to this.**
         * To enable this behavior, you need to specify `enablePrivatePluginApi` in your `manifest.json`.
         */
        readonly fileKey: string | undefined
        /**
         * When enabled, causes all node properties and methods to skip over invisible nodes (and their descendants) inside {@link InstanceNode | instances}.
         * This makes operations like document traversal much faster.
         *
         * Note: Defaults to true in Figma Dev Mode and false in Figma and FigJam
         *
         * @remarks
         *
         * Accessing and modifying invisible nodes and their descendants inside instances can be slow with the plugin API.
         * This is especially true in large documents with tens of thousands of nodes where a call to {@link ChildrenMixin.findAll} might come across many of these invisible instance children.
         *
         * If your plugin does not need access to these nodes, we recommend setting `figma.skipInvisibleInstanceChildren = true` as that often makes document traversal significantly faster.
         *
         * When this flag is enabled, it will not be possible to access invisible nodes (and their descendants) inside instances. This has the following effects:
         *
         * - {@link ChildrenMixin.children} and methods such as {@link ChildrenMixin.findAll} will exclude these nodes.
         * - {@link PluginAPI.getNodeByIdAsync} will return a promise containing null.
         * - {@link PluginAPI.getNodeById} will return null.
         * - Accessing a property on an existing node object for an invisible node will throw an error.
         *
         * For example, suppose that a portion of the document tree looks like this:
         *
         * Frame (visible) → Instance (visible) → Frame (invisible) → Text (visible)
         *
         * The last two frame and text nodes cannot be accessed after setting `figma.skipInvisibleInstanceChildren = true`.
         *
         * The benefit of enabling this flag is that document traversal methods, {@link ChildrenMixin.findAll} and {@link ChildrenMixin.findOne}, can be up to several times faster in large documents that have invisible instance children.
         * {@link ChildrenMixin.findAllWithCriteria} can be up to hundreds of times faster in large documents.
         */
        skipInvisibleInstanceChildren: boolean
        /**
         * Note: This API is only available in FigJam
         *
         * This property contains methods used to read, set, and modify the built in FigJam timer.
         *
         * Read more in the [timer section](https://developers.figma.com/docs/plugins/api/figma-timer).
         */
        readonly timer?: TimerAPI
        /**
         * This property contains methods used to read and set the viewport, the user-visible area of the current page.
         *
         * Read more in the [viewport section](https://developers.figma.com/docs/plugins/api/figma-viewport).
         */
        readonly viewport: ViewportAPI
        /**
         * Note: `currentuser` must be specified in the permissions array in `manifest.json` to access this property.
         *
         * This property contains details about the current user.
         */
        readonly currentUser: User | null
        /**
         * Note: This API is only available in FigJam.
         *
         * `activeusers` must be specified in the permissions array in `manifest.json` to access this property.
         *
         * This property contains details about the active users in the file. `figma.activeUsers[0]` will match `figma.currentUser` for the `id`, `name`, `photoUrl`, `color`, and `sessionId` properties.
         */
        readonly activeUsers: ActiveUser[]
        /**
         * Note: `textreview` must be specified in the capabilities array in `manifest.json` to access this property.
         *
         * This property contains methods that enable text review features in your plugin.
         */
        readonly textreview?: TextReviewAPI
        /**
         * This property contains methods used to integrate with the Dev Mode codegen functionality.
         *
         * Read more in the [codegen section](https://developers.figma.com/docs/plugins/api/figma-codegen).
         */
        readonly codegen: CodegenAPI
        /**
         * This property contains methods used to integrate with the Figma for VS Code extension. If `undefined`, the plugin is not running in VS Code.
         *
         * Read more in [Dev Mode plugins in Visual Studio Code](https://developers.figma.com/docs/plugins/working-in-dev-mode#dev-mode-plugins-in-visual-studio-code)
         */
        readonly vscode?: VSCodeAPI
        /**
         * Caution: This is a private API only available to [Figma partners](https://www.figma.com/partners/)
         */
        readonly devResources?: DevResourcesAPI
        /**
         * Note: `payments` must be specified in the permissions array in `manifest.json` to access this property.
         *
         * This property contains methods for plugins that require payment.
         */
        readonly payments?: PaymentsAPI
        /**
         * Closes the plugin. You should always call this function once your plugin is done running. When called, any UI that's open will be closed and any `setTimeout` or `setInterval` timers will be cancelled.
         *
         * @param message - Optional -- display a visual bell toast with the message after the plugin closes.
         *
         * @remarks
         *
         * Calling `figma.closePlugin()` disables callbacks and Figma APIs. It does not, however, abort the plugin. Any lines of Javascript after this call will also run. For example, consider the following plugin that expects the user to have one layer selected:
         *
         * ```ts title="Simple closePlugin"
         * if (figma.currentPage.selection.length !== 1) {
         *   figma.closePlugin()
         * }
         * figma.currentPage.selection[0].opacity = 0.5
         * ```
         *
         * This will not work. The last line will still run, but will throw an exception because access to `figma.currentPage` has been disabled. As such, it is not recommended to run any code after calling `figma.closePlugin()`.
         *
         * A simple way to easily exit your plugin is to wrap your plugin in a function, instead of running code at the top-level, and always follow `figma.closePlugin()` with a `return` statement:
         *
         * ```ts title="Early return"
         * function main() {
         *   if (figma.currentPage.selection.length !== 1) {
         *     figma.closePlugin()
         *     return
         *   }
         *   figma.currentPage.selection[0].opacity = 0.5
         * }
         * main()
         * ```
         *
         * It's good practice to have all input validation done at the start of the plugin. However, there may be cases where the plugin may need to close after a chain of multiple function calls. If you expect to have to close the plugin deep within your code, but don't want to necessarily want the user to see an error, the example above will not be sufficient.
         *
         * One alternative is to use a top-level try-catch statement. However, you will need to be responsible for making sure that there are no usages of try-catch between the top-level try-catch and the call to `figma.closePlugin()`, or to pass along the close command if necessary. Example:
         *
         * ```ts title="Top-level try-catch"
         * const CLOSE_PLUGIN_MSG = "_CLOSE_PLUGIN_"
         * function someNestedFunctionCallThatClosesThePlugin() {
         *   throw CLOSE_PLUGIN_MSG
         * }
         *
         * function main() {
         *   someNestedFunctionCallThatClosesThePlugin()
         * }
         *
         * try {
         *   main()
         * } catch (e) {
         *   if (e === CLOSE_PLUGIN_MSG) {
         *     figma.closePlugin()
         *   } else {
         *     // >> DO NOT LEAVE THIS OUT <<
         *     // If we caught any other kind of exception,
         *     // it's a real error and should be passed along.
         *     throw e
         *   }
         * }
         * ```
         */
        closePlugin(message?: string): void
        /**
         * Shows a notification on the bottom of the screen.
         *
         * @param message - The message to show. It is limited to 100 characters. Longer messages will be truncated.
         * @param options - An optional argument with the following optional parameters:
         *
         * ```ts
         * interface NotificationOptions {
         *   timeout?: number;
         *   error?: boolean;
         *   onDequeue?: (reason: NotifyDequeueReason) => void
         *   button?: {
         *     text: string
         *     action: () => boolean | void
         *   }
         * }
         * ```
         *
         * - `timeout`: How long the notification stays up in milliseconds before closing. Defaults to 3 seconds when not specified. Set the timeout to `Infinity` to make the notification show indefinitely until the plugin is closed.
         * - `error`: If true, display the notification as an error message, with a different color.
         * - `onDequeue`: A function that will run when the notification is dequeued. This can happen due to the timeout being reached, the notification being dismissed by the user or Figma, or the user clicking the notification's `button`.
         *   - The function is passed a `NotifyDequeueReason`, which is defined as the following:
         * ```ts
         *  type NotifyDequeueReason = 'timeout' | 'dismiss' | 'action_button_click'
         *  ```
         * - `button`: An object representing an action button that will be added to the notification.
         *    - `text`: The message to display on the action button.
         *    - `action`: The function to execute when the user clicks the button. If this function returns `false`, the message will remain when the button is clicked. Otherwise, clicking the action button dismisses the notify message.
         *
         * @remarks
         *
         * The `notify` API is a convenient way to show a message to the user. These messages can be queued.
         *
         * If the message includes a custom action button, it will be closed automatically when the plugin closes.
         *
         * Calling `figma.notify` returns a `NotificationHandler` object. This object contains a single `handler.cancel()` method that can be used to remove the notification before it times out by itself. This is useful if the notification becomes no longer relevant.
         *
         * ```ts
         * interface NotificationHandler {
         *   cancel: () => void
         * }
         * ```
         *
         * An alternative way to show a message to the user is to pass a message to the {@link PluginAPI.closePlugin} function.
         */
        notify(message: string, options?: NotificationOptions): NotificationHandler
        /**
         * Commits actions to undo history. This does not trigger an undo.
         *
         * @remarks
         *
         * By default, plugin actions are not committed to undo history. Call `figma.commitUndo()` so that triggered
         * undos can revert a subset of plugin actions.
         *
         * For example, after running the following plugin code, the first triggered undo will undo both the rectangle and the ellipse:
         * ```ts
         * figma.createRectangle();
         * figma.createEllipse();
         * figma.closePlugin();
         * ```
         * Whereas if we call `commitUndo()` in our plugin, the first triggered undo will only undo the ellipse:
         * ```ts
         * figma.createRectangle();
         * figma.commitUndo();
         * figma.createEllipse();
         * figma.closePlugin();
         * ```
         */
        commitUndo(): void
        /**
         * Triggers an undo action. Reverts to the last `commitUndo()` state.
         */
        triggerUndo(): void
        /**
         * Saves a new version of the file and adds it to the version history of the file. Returns the new version id.
         * @param title - The title of the version. This must be a non-empty string.
         * @param description - An optional argument to describe the version.
         *
         * Calling `saveVersionHistoryAsync` returns a promise that resolves to `null` or an instance of `VersionHistoryResult`:
         *
         * ```ts
         * interface VersionHistoryResult {
         *   id: string
         * }
         * ```
         *
         * - `id`: The version id of this newly saved version.
         *
         * @remarks
         *
         * It is not guaranteed that all changes made before this method is used will be saved to version history.
         * For example,
         *  ```ts title="Changes may not all be saved"
         *  async function example() {
         *    await figma.createRectangle();
         *    await figma.saveVersionHistoryAsync('v1');
         *    figma.closePlugin();
         *  }
         *  example().catch((e) => figma.closePluginWithFailure(e))
         *  ```
         * The newly created rectangle may not be included in the v1 version. As a work around, you can wait before calling `saveVersionHistoryAsync()`. For example,
         *  ```ts title="Wait to save"
         *  async function example() {
         *    await figma.createRectangle();
         *    await new Promise(r => setTimeout(r, 1000)); // wait for 1 second
         *    await figma.saveVersionHistoryAsync('v1');
         *    figma.closePlugin();
         *  }
         * ```
         * Typically, manual changes that precede the execution of `saveVersionHistoryAsync()` will be included. If you want to use `saveVersionHistoryAsync()` before the plugin makes
         * additional changes, make sure to use the method with an async/await or a Promise.
         */
        saveVersionHistoryAsync(title: string, description?: string): Promise<VersionHistoryResult>
        /**
         * Open a url in a new tab.
         *
         * @remarks
         *
         * In the VS Code Extension, this API is required to open a url in the browser. Read more in [Dev Mode plugins in Visual Studio Code](https://developers.figma.com/docs/plugins/working-in-dev-mode#dev-mode-plugins-in-visual-studio-code).
         */
        openExternal(url: string): void
        /**
         * Enables you to render UI to interact with the user, or simply to access browser APIs. This function creates a modal dialog with an `<iframe>` containing the HTML markup in the `html` argument.
         *
         * @param html - The HTML to insert into the iframe. You can pass in the HTML code as a string here, but this will often be the global value [`__html__`](https://developers.figma.com/docs/plugins/api/global-objects#html).
         * @param options - An object that may contain the following optional parameters:
         * - `visible`: Whether the UI starts out displayed to the user. Defaults to `true`. You can use `figma.ui.show()` and `figma.ui.hide()` to change the visibility later.
         * - `width`: The width of the UI. Defaults to 300. Minimum is 70. Can be changed later using `figma.ui.resize(width, height)`
         * - `height`: The height of the UI. Defaults to 200. Minimum is 0. Can be changed later using `figma.ui.resize(width, height)`
         * - `title`: The title of the UI window. Defaults to the plugin name.
         * - `position`: The position of the UI window. Defaults to the last position of the iframe or the center of the viewport. If specified, expects an X/Y coordinate in the canvas space (i.e matches x/y values returned by `<PluginNode>.x` and `<PluginNode>.y`)
         * - `themeColors`: Defaults to `false`. When enabled, CSS variables will be added to the plugin iframe to allow [support for light and dark themes](https://developers.figma.com/docs/plugins/css-variables).
         *
         * Note: If the position specified is outside of the user's viewport, the iframe will be moved so that it remains in the user's viewport.
         *
         * @remarks
         *
         * The easiest way to use this API is to load the HTML file defined in the manifest. This enables writing a separate HTML file which can be accessed through the [`__html__`](https://developers.figma.com/docs/plugins/api/global-objects#html) global variable.
         *
         * If the `<iframe>` UI is already showing when this function is called, the previous UI will be closed before the new one is displayed.
         *
         * ## Usage Examples
         *
         * ```js title="Example usage"
         * figma.showUI(
         *   "<b>Hello from Figma</b>",
         *   { width: 400, height: 200, title: "My title" }
         * )
         *
         * figma.showUI(
         *   "<b>Hello from Figma</b>",
         *   { width: 400, height: 200, title: "My title", position: { x: 100, y: 100 } }
         * )
         *
         * figma.showUI(__html__)
         * ```
         */
        showUI(html: string, options?: ShowUIOptions): void
        /**
         * This property contains methods used to modify and communicate with the UI created via `figma.showUI(...)`.
         *
         * Read more in the [UI section](https://developers.figma.com/docs/plugins/api/figma-ui).
         */
        readonly ui: UIAPI
        /**
         * This property contains convenience functions for common operations.
         *
         * Read more in the [util section](https://developers.figma.com/docs/plugins/api/figma-util).
         */
        readonly util: UtilAPI
        /**
         * This property contains constants that can be accessed by the plugin API.
         *
         * Read more in the [constants section](https://developers.figma.com/docs/plugins/api/figma-constants).
         */
        readonly constants: ConstantsAPI
        /**
         * This property contains methods to store persistent data on the user's local machine.
         *
         * Read more in the [client storage section](https://developers.figma.com/docs/plugins/api/figma-clientStorage).
         */
        readonly clientStorage: ClientStorageAPI
        /**
         * This property contains methods to handle user inputs when a plugin is launched in query mode. See [Accepting Parameters as Input](https://developers.figma.com/docs/plugins/plugin-parameters) for more details.
         */
        readonly parameters: ParametersAPI
        /**
         * Finds a node by its id in the current document. Every node has an `id` property, which is unique within the document. If the id is invalid, or the node cannot be found (e.g. removed), returns a promise containing null.
         */
        getNodeByIdAsync(id: string): Promise<BaseNode | null>
        /**
         * @deprecated Use {@link PluginAPI.getNodeByIdAsync} instead. This function will throw an exception if the plugin manifest contains `"documentAccess": "dynamic-page"`.
         *
         * Finds a node by its id in the current document. Every node has an `id` property, which is unique within the document. If the id is invalid, or the node cannot be found (e.g. removed), returns null.
         */
        getNodeById(id: string): BaseNode | null
        /**
         * Finds a style by its id in the current document. If not found, returns a promise containing null.
         */
        getStyleByIdAsync(id: string): Promise<BaseStyle | null>
        /**
         * @deprecated Use {@link PluginAPI.getStyleByIdAsync} instead. This function will throw an exception if the plugin manifest contains `"documentAccess": "dynamic-page"`.
         *
         * Finds a style by its id in the current document. If not found, returns null.
         */
        getStyleById(id: string): BaseStyle | null
        /**
         * This property contains methods to work with Variables and Variable Collections within Figma.
         *
         * */
        readonly variables: VariablesAPI
        /** This property contains methods to work with assets residing in a team library. */
        readonly teamLibrary: TeamLibraryAPI
        /**
         * This property contains methods to work with annotations.
         *
         */
        readonly annotations: AnnotationsAPI
        /**
         *
         * This API is only available in Buzz.
         *
         * This property contains methods to work in Buzz.
         *
         */
        readonly buzz: BuzzAPI
        /**
         * The root of the entire Figma document. This node is used to access other pages. Each child is a {@link PageNode}.
         */
        readonly root: DocumentNode
        /**
         * The page that the user currently viewing. You can set this value to a {@link PageNode} to switch pages.
         *
         * * If the manifest contains`"documentAccess": "dynamic-page"`, this property is read-only. Use {@link PluginAPI.setCurrentPageAsync} to update the value.
         */
        currentPage: PageNode
        /**
         * Switch the active page to the specified {@link PageNode}.
         */
        setCurrentPageAsync(page: PageNode): Promise<void>
        /**
         * Registers an callback that will be called when an event happens in the editor. Current supported events are:
         * - The selection on the current page changed.
         * - The current page changed.
         * - The document has changed.
         * - An object from outside Figma is dropped onto the canvas
         * - The plugin has started running.
         * - The plugin closed.
         * - The plugin has started running.
         * - The timer has started running.
         * - The timer has paused.
         * - The timer has stopped.
         * - The timer is done.
         * - The timer has resumed.
         *
         *
         * @param type - A string identifying the type of event that the callback will be called on.
         *
         * This is either an `ArgFreeEventType`, `run`, `drop`, or `documentchange`. The `run` event callback will be passed a `RunEvent`. The `drop` event callback will be passed a `DropEvent`. The `documentchange` event callback will be passed a `DocumentChangeEvent`.
         *
         * ```ts
         * type ArgFreeEventType =
         *   "selectionchange" |
         *   "currentpagechange" |
         *   "close" |
         *   "timerstart" |
         *   "timerstop" |
         *   "timerpause" |
         *   "timerresume" |
         *   "timeradjust" |
         *   "timerdone"
         * ```
         *
         * @param callback - A function that will be called when the event happens.
         * If `type` is 'run', then this function will be passed a `RunEvent`.
         * If `type` is 'drop', then this function will be passed a `DropEvent`.
         * If `type` is 'documentchange', then this function will be passed a `DocumentChangeEvent`.
         *
         * Otherwise nothing will be passed in.
         *
         * @remarks
         *
         * This API tries to match Node.js conventions around similar `.on` APIs.
         *
         * It's important to understand that the `.on` API runs the callbacks **asynchronously**. For example:
         *
         * ```ts
         * figma.on("selectionchange", () => { console.log("changed") })
         * console.log("before")
         * figma.currentPage.selection = []
         * console.log("after")
         *
         * // Output:
         * // "before"
         * // "after"
         * // "changed"
         * ```
         *
         * The asynchronous nature of these APIs have a few other implications.
         *
         * The callback will not necessarily be called each time the event happens. For example, this will only trigger the event once:
         *
         * ```ts
         * figma.currentPage.selection = [figma.createRectangle()]
         * figma.currentPage.selection = [figma.createFrame()]
         * ```
         *
         * Nor will the ordering of the event trigger and event registration affect whether the callback is called.
         *
         * ```ts
         * figma.currentPage.selection = [figma.createFrame()]
         * figma.on("selectionchange", () => { "this will get called!" })
         * ```
         *
         * ## Available event types
         *
         * ### `"currentpagechange"`
         *
         * This event will trigger when the user navigates to a different page, or when the plugin changes the value of `figma.currentPage`.
         *
         * ### `"selectionchange"`
         *
         * This event will trigger when the selection of the **current page** changes. This can happen:
         * - By user action.
         * - Due to plugin code.
         * - When the current page changes (a `"currentpagechange"` event always triggers a `"selectionchange"` event).
         * - When a selected node is deleted.
         * - When a selected node becomes the child of another selected node (in which case it is considered indirectly selected, and is no longer in `figma.currentPage.selection`)
         *
         * Note also that changing the selection via the plugin API, then changing it back to its previous value immediately still triggers the event.
         *
         * ### `"documentchange"`
         *
         * If the plugin manifest contains `"documentAccess": "dynamic-page"`, you must first call {@link PluginAPI.loadAllPagesAsync} to access this event. Because this may introduce a loading delay, consider using more granular alternatives, such as the `"stylechange"` event, or using {@link PageNode.on | PageNode.on} with the `"nodechange"` event.
         *
         * This event will trigger when a change is made to the currently open file. The event will be called when nodes/styles are either added, removed, or changed in a document.
         *
         * The callback will be passed with a DocumentChangeEvent with the below interface:
         *
         * ```ts
         * interface DocumentChangeEvent {
         *   documentChanges: DocumentChange[]
         * }
         * ```
         *
         * Note: Note that `DocumentChangeEvent` has a `documentChanges` property with an array of `DocumentChange`s. Figma will not call the 'documentchange' callback synchronously and will instead batch the updates and send them to the callback periodically.
         *
         * There are 6 different {@link DocumentChange} types that we currently notify on and we might add more in the future. Each of these changes has a `type` property to distinguish them:
         *
         * | Change                                                           | `type` property           | Description                                                                                                                                                                                                        |
         * |------------------------------------------------------------------|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
         * | [`CreateChange`](https://developers.figma.com/docs/plugins/api/DocumentChange#createchange)               | `'CREATE'`                | A node has been created in the document. If a node with nested children is being added to the document a `CreateChange` will only be made for the highest level parent that was added to the document.             |
         * | [`DeleteChange`](https://developers.figma.com/docs/plugins/api/DocumentChange#deletechange)               | `'DELETE'`                | A node has been removed from the document. If a node with nested children is being removed from the document a  `DeleteChange`  will only be made for the highest level parent that was removed from the document. |
         * | [`PropertyChange`](https://developers.figma.com/docs/plugins/api/DocumentChange#propertychange)           | `'PROPERTY_CHANGE'`       | A property of a node has changed.                                                                                                                                                                                  |
         * | [`StyleCreateChange`](https://developers.figma.com/docs/plugins/api/DocumentChange#stylecreatechange)     | `'STYLE_CREATE'`          | A style has been added to the document.                                                                                                                                                                            |
         * | [`StyleDeleteChange`](https://developers.figma.com/docs/plugins/api/DocumentChange#styledeletechange)     | `'STYLE_DELETE'`          | A style has been removed from the document.                                                                                                                                                                        |
         * | [`StylePropertyChange`](https://developers.figma.com/docs/plugins/api/DocumentChange#stylepropertychange) | `'STYLE_PROPERTY_CHANGE'` | A style has had a property changed.                                                                                                                                                                                |
         *
         *
         * #### Special cases
         *
         * We currently never notify a `'documentchange'` listener in the following scenarios:
         * - if the change was caused directly by your plugin in a `documentchange` callback
         * - if an instance sublayer was updated by a change to a main component
         * - if a node was updated as a result of a style changing
         *
         * #### Example
         * Here is an example of exhaustively checking changes to the document and logging them to the console.
         *
         * ```ts
         * figma.on("documentchange", (event) => {
         * for (const change of event.documentChanges) {
         *   switch (change.type) {
         *     case "CREATE":
         *       console.log(
         *         `Node ${change.id} created by a ${change.origin.toLowerCase()} user`
         *       );
         *       break;
         *
         *     case "DELETE":
         *       console.log(
         *         `Node ${change.id} deleted by a ${change.origin.toLowerCase()} user`
         *       );
         *       break;
         *
         *     case "PROPERTY_CHANGE":
         *       for (const prop of change.properties) {
         *         console.log(
         *           `Node ${
         *             change.id
         *           } had ${prop} changed by a ${change.origin.toLowerCase()} user`
         *         );
         *       }
         *       break;
         *
         *     case "STYLE_CREATE":
         *       console.log(
         *         `Style ${change.id} created by a ${change.origin.toLowerCase()} user`
         *       );
         *       break;
         *
         *     case "STYLE_DELETE":
         *       console.log(
         *         `Style ${change.id} deleted by a ${change.origin.toLowerCase()} user`
         *       );
         *       break;
         *
         *      case "STYLE_PROPERTY_CHANGE":
         *        for (const prop of change.properties) {
         *          console.log(
         *             `Style ${
         *               change.id
         *             } had ${prop} changed by a ${change.origin.toLowerCase()} user`
         *           );
         *        }
         *        break;
         *     }
         *   }
         * });
         * ```
         *
         * For a more involved example see our [plugin samples on GitHub](https://github.com/figma/plugin-samples/tree/master/document-change).
         *
         * ### `"textreview"`
         *
         * Note: This event is only available to plugins that have the `"textreview"` capability in their `manifest.json` and the plugin is running in text review mode.
         *
         * `"textreview"` events allow plugins to review text in a document and act as either a replacement or a supplement to native spell check.
         *
         * This event is triggered periodically when the user is typing in a text node. The callback will be passed with a TextReviewEvent with the below interface:
         * ```ts
         * interface TextReviewEvent {
         *   text: string
         * }
         * ```
         *
         * The `text` property is the text that the user has currently typed into the node.
         *
         * A `"textreview"` event listener should return a promise that resolves to an array of `TextReviewRange` objects. Each `TextReviewRange` object represents a single range of text that should be marked as either an error or a suggestion. The `TextReviewRange` interface is defined as:
         * ```ts
         * type TextReviewRange = {
         *   start: number
         *   end: number
         *   suggestions: string[]
         *   color?: 'RED' | 'GREEN' | 'BLUE'
         * }
         * ```
         *
         * The `start` property is the index of the first character in the range. The `end` property is the index of the last character in the range. The `suggestions` property is an array of strings that represent the suggestions for the range. The `color` property is optional and can be used to change the color of the underline that is drawn under the range. If no color is specified the underline will be red.
         *
         * For more information read our in depth guide on [text review plugins](https://developers.figma.com/docs/plugins/textreview-plugins).
         *
         * ### `"drop"`
         *
         * This event will trigger when objects outside Figma (such as elements from other browser windows, or files from the local filesystem) are dropped onto the canvas.
         *
         * It can also be triggered by a special `pluginDrop` message sent from the UI. See the [Triggering drop events from the UI](https://developers.figma.com/docs/plugins/creating-ui#triggering-drop-events-from-the-ui) section for more details.
         *
         * The callback will be passed a `DropEvent` with the below interface. It should return `false` if it wants to handle the particular drop and stop Figma from performing the default drop behavior.
         * ```ts
         * interface DropEvent {
         *   node: BaseNode | SceneNode
         *   x: number
         *   y: number
         *   absoluteX: number
         *   absoluteY: number
         *   items: DropItem[]
         *   files: DropFile[]
         *   dropMetadata?: any
         * }
         * ```
         *
         * - The `node` property contains the node where the drop landed. This will sometimes be the page node if the drop didn't land on anything in the canvas, or if target node is locked or cannot be a parent of another node.
         * - The `x` and `y` properties are coordinates relative to the node drop target
         * - The `absoluteX` and `absoluteY` properties are absolute canvas coordinates
         * - The `items` property is an array of `DropItem` objects. You will see multiple objects if a drop contains multiple, non-file data types. If there are no data items, this array will be empty.
         * - The `files` property is an array of dropped files represented as `DropFile` objects. If no files are present, this array will be empty.
         * - The `dropMetadata` property comes from drop events [explicitly triggered by the UI](https://developers.figma.com/docs/plugins/creating-ui#triggering-drop-events-from-the-ui).
         *
         * Items and files will conform to the below interfaces:
         *
         * ```ts
         * interface DropItem {
         *   type: string // e.g. "text/html", "text/uri-list", etc...
         *   data: string
         * }
         *
         * interface DropFile {
         *   name: string // file name
         *   type: string // e.g. "image/png"
         *   getBytesAsync(): Promise<Uint8Array> // get raw file bytes
         *   getTextAsync(): Promise<string> // get text assuming file is UTF8-encoded
         * }
         * ```
         *
         * See the Icon Drag-and-Drop and PNG Crop examples in the [figma/plugin-samples](https://github.com/figma/plugin-samples) repository for plugins that implement this API.
         *
         * #### UI Recommendations
         *
         * When the plugin registers a drop callback, it should give the user instructions with either text in the plugin UI or [`figma.notify()`](https://developers.figma.com/docs/plugins/api/properties/figma-notify) (if the plugin does not show a UI) telling them what to do.
         *
         * [`figma.notify()`](https://developers.figma.com/docs/plugins/api/properties/figma-notify) can be called with the `timeout` option set to `Infinity` to make the notification show for as long as the plugin is open.
         *
         * ### `"close"`
         *
         * This event will trigger when the plugin is about to close, either from a call to `figma.closePlugin()` or the user closing the plugin via the UI.
         *
         * This is a good place to run cleanup actions. For example, some plugins add UI elements in the canvas by creating nodes. These UI elements should be deleted when the plugin is closed. Note that you don't need to call `figma.closePlugin()` again in this function.
         *
         * **You should use this API only if strictly necessary, and run as little code as possible in the callback when doing so**. When a user closes a plugin, they expect it to be closed immediately. Having long-running actions in the closing callback prevents the plugin for closing promptly.
         *
         * This is also not the place to run any asynchronous actions (e.g. register callbacks, using `await`, etc). The plugin execution environment will be destroyed immediately when all the callbacks have returned, and further callbacks will not be called.
         *
         * ### `"run"`
         *
         * This event is triggered when a plugin is run. For plugins with parameters, this happens after all parameters have been enter by the user in the quick action UI. For all other plugins this happens immediately after launch.
         *
         * The callback will be passed a `RunEvent` that looks like:
         * ```ts
         * interface RunEvent {
         *   parameters?: ParameterValues
         *   command: string
         * }
         * ```
         *
         * - The `parameters` property is of type [`ParameterValues`](https://developers.figma.com/docs/plugins/api/figma-parameters#parametervalues), and contains the value entered for each parameter.
         * - The `command` argument is the same as [`figma.command`](https://developers.figma.com/docs/plugins/api/figma#command), but provided here again for convenience.
         *
         * Handling the `run` event is only required for plugins with parameters. For all plugins it can still be a convenient spot to put your top level code, since it is called
         * on every plugin run.
         *
         * ### `"stylechange"`
         *
         * Triggered when any styles in the document change.
         *
         * The callback will receive a StyleChangeEvent with the below interface:
         *
         * ```ts
         * interface StyleChangeEvent {
         *   styleChanges: StyleChange[]
         * }
         * ```
         *
         * There are 3 different {@link StyleChange} types. Each of these changes has a `type` property to distinguish them:
         *
         * | Change | `type` property | Description |
         * | --- | --- | --- |
         * | [`StyleCreateChange`](https://developers.figma.com/docs/plugins/api/StyleChange#stylecreatechange) | `'STYLE_CREATE'` | A style has been added to the document. |
         * | [`StyleDeleteChange`](https://developers.figma.com/docs/plugins/api/StyleChange#styledeletechange) | `'STYLE_DELETE'` | A style has been removed from the document. |
         * | [`StylePropertyChange`](https://developers.figma.com/docs/plugins/api/StyleChange#stylepropertychange) | `'STYLE_PROPERTY_CHANGE'` | A style has had a property changed. |
         *
         * ### `"timerstart"`
         *
         * This event will trigger when somebody starts a timer in the document. This can happen either by a user (either the current user or a multiplayer user) starting the timer from the UI, or triggered by plugin code. To inspect the current state of the timer when this event fires, use the `figma.timer` interface. For example:
         * ```ts
         * figma.on("timerstart", () => console.log(figma.timer.remaining))
         * figma.timer.start(300)
         *
         * // Output:
         * // 300
         * ```
         *
         * ### `"timerpause"`
         *
         * Triggered when a timer that is running is paused.
         *
         * ### `"timerstop"`
         *
         * Triggered when the timer is stopped.
         *
         * ### `"timerdone"`
         *
         * Triggered when the timer is running and reaches 0 time remaining.
         *
         * ### `"timerresume"`
         *
         * Triggered when a timer that is paused is resumed.
         *
         * ### `"timeradjust"`
         *
         * Triggered when the total time on the timer changes. From the UI, it is only possible to add time to the timer. However, plugin code can both add and remove time from a running timer.
         */
        on(type: ArgFreeEventType, callback: () => void): void
        on(type: 'run', callback: (event: RunEvent) => void): void
        on(type: 'drop', callback: (event: DropEvent) => boolean): void
        on(type: 'documentchange', callback: (event: DocumentChangeEvent) => void): void
        on(type: 'slidesviewchange', callback: (event: SlidesViewChangeEvent) => void): void
        on(type: 'canvasviewchange', callback: (event: CanvasViewChangeEvent) => void): void
        on(
          type: 'textreview',
          callback: (event: TextReviewEvent) => Promise<TextReviewRange[]> | TextReviewRange[],
        ): void
        on(type: 'stylechange', callback: (event: StyleChangeEvent) => void): void
        /**
         * Same as `figma.on`, but the callback will only be called once, the first time the specified event happens.
         */
        once(type: ArgFreeEventType, callback: () => void): void
        once(type: 'run', callback: (event: RunEvent) => void): void
        once(type: 'drop', callback: (event: DropEvent) => boolean): void
        once(type: 'documentchange', callback: (event: DocumentChangeEvent) => void): void
        once(type: 'slidesviewchange', callback: (event: SlidesViewChangeEvent) => void): void
        once(type: 'canvasviewchange', callback: (event: CanvasViewChangeEvent) => void): void
        once(
          type: 'textreview',
          callback: (event: TextReviewEvent) => Promise<TextReviewRange[]> | TextReviewRange[],
        ): void
        once(type: 'stylechange', callback: (event: StyleChangeEvent) => void): void
        /**
         * Removes a callback added with `figma.on` or `figma.once`.
         *
         * @remarks
         *
         * The callback needs to be the same object that was originally added. For example, you can do this:
         *
         * ```ts title="Correct way to remove a callback"
         * let fn = () => { console.log("selectionchanged") }
         * figma.on("selectionchange", fn)
         * figma.off("selectionchange", fn)
         * ```
         *
         * whereas the following won't work, because the function objects are different:
         *
         * ```ts title="Incorrect way to remove a callback"
         * figma.on("selectionchange", () => { console.log("selectionchanged") })
         * figma.off("selectionchange", () => { console.log("selectionchanged") })
         * ```
         */
        off(type: ArgFreeEventType, callback: () => void): void
        off(type: 'run', callback: (event: RunEvent) => void): void
        off(type: 'drop', callback: (event: DropEvent) => boolean): void
        off(type: 'documentchange', callback: (event: DocumentChangeEvent) => void): void
        off(type: 'slidesviewchange', callback: (event: SlidesViewChangeEvent) => void): void
        off(type: 'canvasviewchange', callback: (event: CanvasViewChangeEvent) => void): void
        off(
          type: 'textreview',
          callback: (event: TextReviewEvent) => Promise<TextReviewRange[]> | TextReviewRange[],
        ): void
        off(type: 'stylechange', callback: (event: StyleChangeEvent) => void): void
        /**
         * This a constant value that some node properties return when they are a mix of multiple values. An example might be font size: a single text node can use multiple different font sizes for different character ranges. For those properties, you should always compare against `figma.mixed`.
         *
         * @remarks
         *
         * Example:
         *
         * ```ts title="Check if property is a mix of multiple values"
         * if (node.type === 'RECTANGLE') {
         *   if (node.cornerRadius !== figma.mixed) {
         *     console.log(`Single corner radius: ${node.cornerRadius}`)
         *   } else {
         *     console.log(`Mixed corner radius: ${node.topLeftRadius}, ${node.topRightRadius}, ${node.bottomLeftRadius}, ${node.bottomRightRadius}`)
         *   }
         * }
         * ```
         *
         * Note: Your plugin never needs to know what the actual value of `figma.mixed` is, only that it is a unique, constant value that can be compared against. That being said, this value returns an object of type `symbol` which is a more advanced feature of Javascript. [Read more about symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol). It works in TypeScript via the `unique symbol` [subtype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol).
         */
        readonly mixed: unique symbol
        /**
         * Creates a new rectangle. The behavior is similar to using the `R` shortcut followed by a click.
         *
         * @remarks
         *
         * By default, the new node has a default fill, width and height both at 100, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a rectangle and set basic styles"
         * const rect = figma.createRectangle()
         *
         * // Move to (50, 50)
         * rect.x = 50
         * rect.y = 50
         *
         * // Set size to 200 x 100
         * rect.resize(200, 100)
         *
         * // Set solid red fill
         * rect.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
         * ```
         */
        createRectangle(): RectangleNode
        /**
         * Creates a new line.
         *
         * @remarks
         *
         * By default, the new node is 100 in width, has a black stroke, with weight 1, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a line and set basic styles"
         * const line = figma.createLine()
         *
         * // Move to (50, 50)
         * line.x = 50
         * line.y = 50
         *
         * // Make line 200px long
         * line.resize(200, 0)
         *
         * // 4px thick red line with arrows at each end
         * line.strokeWeight = 4
         * line.strokes = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
         * line.strokeCap = 'ARROW_LINES'
         * ```
         */
        createLine(): LineNode
        /**
         * Creates a new ellipse. The behavior is similar to using the `O` shortcut followed by a click.
         *
         * @remarks
         *
         * By default, the new node has a default fill, width and height both at 100, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a red, U-shaped half donut"
         * const ellipse = figma.createEllipse()
         *
         * // Move to (50, 50)
         * ellipse.x = 50
         * ellipse.y = 50
         *
         * // Set size to 200 x 100
         * ellipse.resize(200, 100)
         *
         * // Set solid red fill
         * ellipse.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
         *
         * // Arc from 0° to 180° clockwise
         * ellipse.arcData = {startingAngle: 0, endingAngle: Math.PI, innerRadius: 0.5}
         * ```
         */
        createEllipse(): EllipseNode
        /**
         * Creates a new polygon (defaults to a triangle).
         *
         * @remarks
         *
         * By default, the new node has three edges (i.e. a triangle), a default fill, width and height both at 100, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a red octagon"
         * const polygon = figma.createPolygon()
         *
         * // Move to (50, 50)
         * polygon.x = 50
         * polygon.y = 50
         *
         * // Set size to 200 x 200
         * polygon.resize(200, 200)
         *
         * // Make the polygon 8-sided
         * polygon.pointCount = 8
         *
         * // Set solid red fill
         * polygon.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
         * ```
         */
        createPolygon(): PolygonNode
        /**
         * Creates a new star.
         *
         * @remarks
         *
         * By default, the new node has five points edges (i.e. a canonical star), a default fill, width and height both at 100, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a red, 7-pointed star"
         * const star = figma.createStar()
         *
         * // Move to (50, 50)
         * star.x = 50
         * star.y = 50
         *
         * // Set size to 200 x 200
         * star.resize(200, 200)
         *
         * // Make the star 7-pointed
         * star.pointCount = 7
         *
         * // Set solid red fill
         * star.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
         *
         * // Make the angles of each point less acute
         * star.innerRadius = 0.6
         * ```
         */
        createStar(): StarNode
        /**
         * Creates a new, empty vector network with no vertices.
         *
         * @remarks
         *
         * By default, parented under `figma.currentPage`. Without setting additional properties, the vector has a bounding box but doesn't have any vertices. There are two ways to assign vertices to a vector node - [`vectorPaths`](https://developers.figma.com/docs/plugins/api/VectorNode#vectorpaths) and [`setVectorNetworkAsync`](https://developers.figma.com/docs/plugins/api/VectorNode#setvectornetworkasync). Please refer to the documentation of those properties for more details.
         */
        createVector(): VectorNode
        /**
         * Creates a new, empty text node.
         *
         * @remarks
         *
         * By default, parented under `figma.currentPage`. Without setting additional properties, the text has no characters. You can assign a string, to the [`characters`](https://developers.figma.com/docs/plugins/api/properties/TextNode-characters) property of the returned node to provide it with text.
         *
         * ```ts title="Create a styled 'Hello world!' text node"
         * (async () => {
         *   const text = figma.createText()
         *
         *   // Move to (50, 50)
         *   text.x = 50
         *   text.y = 50
         *
         *   // Load the font in the text node before setting the characters
         *   await figma.loadFontAsync(text.fontName)
         *   text.characters = 'Hello world!'
         *
         *   // Set bigger font size and red color
         *   text.fontSize = 18
         *   text.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
         * })()
         * ```
         */
        createText(): TextNode
        /**
         * Creates a new frame. The behavior is similar to using the `F` shortcut followed by a click.
         *
         * @remarks
         *
         * By default, the new node has a white background, width and height both at 100, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a frame"
         * const frame = figma.createFrame()
         *
         * // Move to (50, 50)
         * frame.x = 50
         * frame.y = 50
         *
         * // Set size to 1280 x 720
         * frame.resize(1280, 720)
         * ```
         */
        createFrame(): FrameNode
        /**
         * Note: This API is only available in Figma Design
         *
         * Creates a new, empty component.
         *
         * @remarks
         *
         * By default, the new node has width and height both at 100, and is parented under `figma.currentPage`.
         *
         * This function creates a brand new component. To create a component from an existing node, use {@link PluginAPI.createComponentFromNode}.
         *
         * ```ts title="Create a component"
         * const component = figma.createComponent()
         * ```
         */
        createComponent(): ComponentNode
        /**
         * Note: This API is only available in Figma Design
         *
         * Creates a component from an existing node, preserving all of its properties and children. The behavior is similar to using the **Create component** button in the toolbar.
         *
         * @remarks
         *
         * To create a brand new component instead, use {@link PluginAPI.createComponent}.
         *
         * There are many restrictions on what nodes can be turned into components. For example, the node cannot be a component or component set and cannot be inside a component, component set, or instance.
         *
         * If you try to create a component from a node that cannot be turned into a component, then the function will throw a `Cannot create component from node` error.
         *
         * ```ts title="Create a component from a node"
         * const frame = figma.createFrame()
         * const component = figma.createComponentFromNode(frame)
         */
        createComponentFromNode(node: SceneNode): ComponentNode
        /**
         * Note: This API is only available in Figma Design
         *
         * Creates a new page, appended to the document's list of children.
         *
         * @remarks
         *
         * A page node can be the parent of all types of nodes except for the document node and other page nodes.
         *
         * Files in a Starter team are limited to three pages. When a plugin tries to create more than three pages in a Starter team file, it triggers the following error:
         *
         * ```text title="Page limit error"
         * The Starter plan only comes with 3 pages. Upgrade to
         * Professional for unlimited pages.
         * ```
         */
        createPage(): PageNode
        /**
         * Creates a new page divider, appended to the document's list of children. A page divider is a {@link PageNode} with `isPageDivider` true.
         *
         * @remarks
         *
         * A page divider is always the child of the document node and cannot have any children.
         *
         * @param dividerName - An optional argument to specify the name of the page divider node. It won't change how the page divider appears in the UI, but it specifies the name of the underlying node. The dividerName must be a page divider name (all asterisks, all en dashes, all em dashes, or all spaces). If no dividerName is specified, the default name for the created page divider node is "---".
         */
        createPageDivider(dividerName?: string): PageNode
        /**
           * Creates a new slice object.
           *
           * @remarks
           *
           * By default, the new node is parented under `figma.currentPage`.
           *
           * ```ts title="Create a slice and export as PNG"
           * (async () => {
           *   const slice = figma.createSlice()
           *
           *   // Move to (50, 50)
           *   slice.x = 50
           *   slice.y = 50
           *
           *   // Set size to 500 x 500
           *   slice.resize(500, 500)
           *
           *   // Export a PNG of this region of the canvas
           *   const bytes = await slice.exportAsync()
           *
           *   // Add the image onto the canvas as an image fill in a frame
           *   const image = figma.createImage(bytes)
           *   const frame = figma.createFrame()
           *   frame.resize(500, 500)
           *   frame.fills = [{
           *     imageHash: image.hash,
           *     scaleMode: "FILL",
           *     scalingFactor: 1,
           *     type: "IMAGE",
           *   }]
        })()
           * ```
           */
        createSlice(): SliceNode
        /**
         * Note: This API is only available in Figma Slides
         *
         * @remarks
         *
         * By default, the slide gets appended to the end of the presentation (the last child in the last Slide Row).
         *
         * ```ts title="Create a slide"
         * const slide = figma.createSlide()
         * ```
         *
         * To specify a position in the Slide Grid, pass a row and column index to the function.
         *
         * ```ts title="Create a slide at index 0, 0"
         * const slide = figma.createSlide(0, 0)
         * ```
         */
        createSlide(row?: number, col?: number): SlideNode
        /**
         * Note: This API is only available in Figma Slides
         *
         * Creates a new Slide Row, which automatically gets appended to the Slide Grid.
         *
         * @remarks
         *
         * By default, the row gets appended to the end of the Slide Grid.
         *
         * ```ts title="Create a slide row"
         * const slideRow = figma.createSlideRow()
         * ```
         *
         * To specify a position in the Slide Grid, pass a row index to the function.
         *
         * ```ts title="Create a slide row at index 0"
         * const slideRow = figma.createSlideRow(0)
         * ```
         */
        createSlideRow(row?: number): SlideRowNode
        /**
         * Note: This API is only available in FigJam
         *
         * Creates a new sticky. The behavior is similar to using the `S` shortcut followed by a click.
         *
         * @remarks
         *
         * By default, the new node has constant width and height both at 240, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a sticky with text"
         * (async () => {
         *   const sticky = figma.createSticky()
         *
         *   // Load the font before setting characters
         *   await figma.loadFontAsync(sticky.text.fontName)
         *   sticky.text.characters = 'Hello world!'
         * })()
         * ```
         */
        createSticky(): StickyNode
        /**
         * Note: This API is only available in FigJam
         *
         * Creates a new connector. The behavior is similar to using the `Shift-C` shortcut followed by a click.
         *
         * @remarks
         *
         * By default, the new node has a width of 200, and is parented under `figma.currentPage`.
         *
         * ```ts title="Add a connector between two stickies"
         * // Create two stickies
         * const stickyLeft = figma.createSticky()
         * stickyLeft.x = -200
         *
         * const stickyRight = figma.createSticky()
         * stickyRight.x = 200
         *
         * // Connect the two stickies
         * const connector = figma.createConnector()
         * connector.connectorStart = {
         *   endpointNodeId: stickyLeft.id,
         *   magnet: 'AUTO'
         * }
         *
         * connector.connectorEnd = {
         *   endpointNodeId: stickyRight.id,
         *   magnet: 'AUTO'
         * }
         * ```
         */
        createConnector(): ConnectorNode
        /**
         * Note: This API is only available in FigJam
         *
         * Creates a new shape with text.
         *
         * @remarks
         *
         * By default, the new node has a width and height of 208, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a rounded rectangle shape with text"
         * (async () => {
         *   const shape = figma.createShapeWithText()
         *   shape.shapeType = 'ROUNDED_RECTANGLE'
         *
         *   // Load the font before setting characters
         *   await figma.loadFontAsync(shape.text.fontName)
         *   shape.text.characters = 'Hello world!'
         * })()
         * ```
         */
        createShapeWithText(): ShapeWithTextNode
        /**
         * Note: This API is only available in FigJam
         *
         * Creates a new code block.
         */
        createCodeBlock(): CodeBlockNode
        /**
         *
         * Creates a new section
         */
        createSection(): SectionNode
        /**
         * Note: This API is only available in FigJam
         *
         * Creates a new table.
         *
         * @remarks
         *
         * By default, a table has two rows and two columns, and is parented under `figma.currentPage`.
         *
         * ```ts title="Create a table and add text to cells inside"
         * (async () => {
         *   // Create a table with 2 rows and 3 columns
         *   const table = figma.createTable(2, 3)
         *
         *   // Load the font before setting characters
         *   await figma.loadFontAsync(table.cellAt(0, 0).text.fontName)
         *
         *   // Sets characters for the table cells:
         *   // A B C
         *   // 1 2 3
         *   table.cellAt(0, 0).text.characters = 'A'
         *   table.cellAt(0, 1).text.characters = 'B'
         *   table.cellAt(0, 2).text.characters = 'C'
         *   table.cellAt(1, 0).text.characters = '1'
         *   table.cellAt(1, 1).text.characters = '2'
         *   table.cellAt(1, 2).text.characters = '3'
         * })()
         * ```
         */
        createTable(numRows?: number, numColumns?: number): TableNode
        /**
         * Creates a new text on a path node from an existing vector node.
         *
         * @remarks
         * Once you create a TextPathNode, you can then modify properties such as `characters`, `fontSize`, `fill`, etc just like a regular TextNode.
         *
         * Example:
         * ```ts
         * const circle = figma.createEllipse()
         * circle.resize(200, 200)
         * await figma.loadFontAsync({ family: "Inter", style: "Regular" })
         * const textPath = figma.createTextPath(circle, 2, 0.5)
         * textPath.characters = "This is text on a path!"
         * ```
         * The base vector network cannot currently be modified after creating the TextPathNode.
         *
         * Note: Creating a `TextPathNode` modifies the `type` of the underlying node. Make sure that you use the node object returned from this function rather than the original node object.
         *
         *
         * @param node - The vector-like node to convert to a text on a path node. These can be VectorNodes, shape nodes (Rectangle, Ellipse, Polygon, Star), or Line nodes.
         * @param startSegment - The index of the segment in the vector network to start the text path from.
         * @param startPosition - A number between 0 a
    • plugin-api-standalone.index.md 27.9 KB
      # Plugin API Index
      
      > Full typings: `plugin-api-standalone.d.ts` (11,292 lines)
      > Grep by symbol name to jump to definition. All `L#` line numbers refer to that file.
      
      ---
      
      ## figma.\* — PluginAPI (L24)
      
      ### Identity & State
      
      | Member                          | Type                                                                             |
      | ------------------------------- | -------------------------------------------------------------------------------- |
      | `apiVersion`                    | `'1.0.0'`                                                                        |
      | `editorType`                    | `'figma' \| 'figjam' \| 'dev' \| 'slides' \| 'buzz'`                             |
      | `mode`                          | `'default' \| 'textreview' \| 'inspect' \| 'codegen' \| 'linkpreview' \| 'auth'` |
      | `fileKey`                       | `string \| undefined`                                                            |
      | `root`                          | `DocumentNode`                                                                   |
      | `currentPage`                   | `PageNode` — assign via `setCurrentPageAsync`                                    |
      | `currentUser`                   | `User \| null`                                                                   |
      | `mixed`                         | `unique symbol` — sentinel for mixed values in selection                         |
      | `skipInvisibleInstanceChildren` | `boolean`                                                                        |
      
      ### Navigation & Lookup
      
      | Method                      | Returns                                                 |
      | --------------------------- | ------------------------------------------------------- |
      | `setCurrentPageAsync(page)` | `Promise<void>` — **MUST use this**; sync setter throws |
      | `getNodeByIdAsync(id)`      | `Promise<BaseNode \| null>`                             |
      | `getNodeById(id)`           | `BaseNode \| null`                                      |
      | `getStyleByIdAsync(id)`     | `Promise<BaseStyle \| null>`                            |
      | `getStyleById(id)`          | `BaseStyle \| null`                                     |
      
      ### Create Nodes
      
      | Method                              | Returns                     |
      | ----------------------------------- | --------------------------- |
      | `createFrame()`                     | `FrameNode`                 |
      | `createComponent()`                 | `ComponentNode`             |
      | `createComponentFromNode(node)`     | `ComponentNode`             |
      | `createRectangle()`                 | `RectangleNode`             |
      | `createEllipse()`                   | `EllipseNode`               |
      | `createLine()`                      | `LineNode`                  |
      | `createPolygon()`                   | `PolygonNode`               |
      | `createStar()`                      | `StarNode`                  |
      | `createVector()`                    | `VectorNode`                |
      | `createText()`                      | `TextNode`                  |
      | `createSection()`                   | `SectionNode`               |
      | `createPage()`                      | `PageNode`                  |
      | `createSlice()`                     | `SliceNode`                 |
      | `createBooleanOperation()`          | `BooleanOperationNode`      |
      | `createTable(rows?, cols?)`         | `TableNode`                 |
      | `createImage(data: Uint8Array)`     | `Image`                     |
      | `createNodeFromSvg(svg)`            | `FrameNode`                 |
      | `createNodeFromJSXAsync(jsx)`       | `Promise<SceneNode>`        |
      | `importComponentByKeyAsync(key)`    | `Promise<ComponentNode>`    |
      | `importComponentSetByKeyAsync(key)` | `Promise<ComponentSetNode>` |
      | `importStyleByKeyAsync(key)`        | `Promise<BaseStyle>`        |
      
      ### Styles (Local)
      
      | Method                             | Returns         |
      | ---------------------------------- | --------------- |
      | `createPaintStyle()`               | `PaintStyle`    |
      | `createTextStyle()`                | `TextStyle`     |
      | `createEffectStyle()`              | `EffectStyle`   |
      | `createGridStyle()`                | `GridStyle`     |
      | `getLocalPaintStyles()` / `Async`  | `PaintStyle[]`  |
      | `getLocalTextStyles()` / `Async`   | `TextStyle[]`   |
      | `getLocalEffectStyles()` / `Async` | `EffectStyle[]` |
      | `getLocalGridStyles()` / `Async`   | `GridStyle[]`   |
      
      ### Fonts
      
      | Method                      | Notes                              |
      | --------------------------- | ---------------------------------- |
      | `loadFontAsync(fontName)`   | **MUST call before any text edit** |
      | `listAvailableFontsAsync()` | `Promise<Font[]>`                  |
      | `hasMissingFont`            | `boolean`                          |
      
      ### Plugin Lifecycle
      
      | Method                                  | Notes                                                        |
      | --------------------------------------- | ------------------------------------------------------------ |
      | `closePlugin(message?)`                 | **MUST call on success paths**                               |
      | `closePluginWithFailure(message?)`      | **MUST call in catch blocks — never use closePlugin for errors** |
      | `commitUndo()`                          | Snapshot to undo history                                     |
      | `triggerUndo()`                         | Revert to last snapshot                                      |
      | `saveVersionHistoryAsync(title, desc?)` | `Promise<VersionHistoryResult>`                              |
      | `notify(message, options?)`             | **throws "not implemented" in use_figma — do not use** |
      | `openExternal(url)`                     | Opens URL in browser                                         |
      
      ### Sub-APIs (properties on figma)
      
      | Property              | Interface                | L#    |
      | --------------------- | ------------------------ | ----- |
      | `figma.variables`     | `VariablesAPI`           | L2016 |
      | `figma.ui`            | `UIAPI`                  | L2604 |
      | `figma.util`          | `UtilAPI`                | L2691 |
      | `figma.constants`     | `ConstantsAPI`           | L2809 |
      | `figma.clientStorage` | `ClientStorageAPI`       | L2531 |
      | `figma.viewport`      | `ViewportAPI`            | L3086 |
      | `figma.parameters`    | `ParametersAPI`          | L3292 |
      | `figma.teamLibrary`   | `TeamLibraryAPI`         | L2372 |
      | `figma.annotations`   | `AnnotationsAPI`         | L2187 |
      | `figma.codegen`       | `CodegenAPI`             | L2871 |
      | `figma.textreview?`   | `TextReviewAPI`          | L3166 |
      | `figma.payments?`     | `PaymentsAPI`            | L2420 |
      | `figma.buzz`          | `BuzzAPI`                | L2211 |
      | `figma.timer?`        | `TimerAPI` (FigJam only) | L3053 |
      
      ---
      
      ## VariablesAPI — figma.variables (L2016)
      
      ```
      getVariableById(id)                      Variable | null
      getVariableByIdAsync(id)                 Promise<Variable | null>
      getVariableCollectionById(id)            VariableCollection | null
      getVariableCollectionByIdAsync(id)       Promise<VariableCollection | null>
      getLocalVariables(type?)                 Variable[]           ← sync works; filter by VariableResolvedDataType
      getLocalVariablesAsync(type?)            Promise<Variable[]>
      getLocalVariableCollections()            VariableCollection[] ← sync works
      getLocalVariableCollectionsAsync()       Promise<VariableCollection[]> ← may not be available; use sync
      createVariable(name, collection, type)   Variable
      createVariableCollection(name)           VariableCollection
      createVariableAlias(variable)            VariableAlias
      importVariableByKeyAsync(key)            Promise<Variable>
      setBoundVariableForPaint(paint, field, variable)    → returns NEW paint — reassign
      setBoundVariableForEffect(effect, field, variable)  → returns NEW effect — reassign
      setBoundVariableForLayoutGrid(grid, field, variable)
      ```
      
      **Variable (L10204):** `name`, `resolvedType`, `codeSyntax`, `scopes`, `hiddenFromPublishing`, `valuesByMode`, `variableCollectionId`
      
      - `setVariableCodeSyntax(platform, value)` — platform: `'WEB' | 'ANDROID' | 'iOS'`
      - `setValueForMode(collectionId, modeId, value)`
      - `remove()`
      
      **VariableCollection (L10418):** `name`, `modes`, `variableIds`, `defaultModeId`, `hiddenFromPublishing`
      
      - `addMode(name)` → `modeId`; `removeMode(modeId)`; `renameMode(modeId, name)`
      
      ---
      
      ## Node Types
      
      ### Concrete Scene Nodes
      
      | Node                   | L#     | Key characteristics                                |
      | ---------------------- | ------ | -------------------------------------------------- |
      | `DocumentNode`         | L8960  | Root; `children: PageNode[]`                       |
      | `PageNode`             | L9119  | `children`, local styles, `backgrounds`            |
      | `FrameNode`            | L9311  | `DefaultFrameMixin` — auto-layout, clips, children |
      | `GroupNode`            | L9321  | Children only, no auto-layout                      |
      | `ComponentNode`        | L9678  | Like Frame + publishable                           |
      | `ComponentSetNode`     | L9653  | Variant set container                              |
      | `InstanceNode`         | L9719  | Like Frame; `mainComponent`, `detach()`            |
      | `RectangleNode`        | L9378  | `DefaultShapeMixin` + corners                      |
      | `EllipseNode`          | L9410  | + `arcData`                                        |
      | `LineNode`             | L9396  |                                                    |
      | `PolygonNode`          | L9430  |                                                    |
      | `StarNode`             | L9450  |                                                    |
      | `VectorNode`           | L9476  | Vector paths                                       |
      | `TextNode`             | L9493  | Rich text, fonts, segments                         |
      | `TextPathNode`         | L9564  | Text along path                                    |
      | `BooleanOperationNode` | L9792  | `booleanOperation` property                        |
      | `SliceNode`            | L9368  | Export only                                        |
      | `SectionNode`          | L10754 | Grouping + fills                                   |
      | `TableNode`            | L9862  | `TableCellNode` children                           |
      
      **FigJam only:** `StickyNode` L9812, `ConnectorNode` L10121, `ShapeWithTextNode` L9999, `StampNode` L9838, `CodeBlockNode` L10080, `EmbedNode` L10661, `LinkUnfurlNode` L10701, `MediaNode` L10721
      
      **Slides only:** `SlideNode` L10784, `SlideRowNode` L10809, `SlideGridNode` L10822
      
      **Union types:**
      
      ```
      type SceneNode  (L10917) = FrameNode | GroupNode | SliceNode | RectangleNode | LineNode
        | EllipseNode | PolygonNode | StarNode | VectorNode | TextNode | ComponentSetNode
        | ComponentNode | InstanceNode | BooleanOperationNode | SectionNode | ...
      type BaseNode   (L10913) = DocumentNode | PageNode | SceneNode
      ```
      
      ---
      
      ## Mixin Interfaces
      
      | Mixin                        | L#    | Provides                                                                                        |
      | ---------------------------- | ----- | ----------------------------------------------------------------------------------------------- |
      | `BaseNodeMixin`              | L5284 | `id`, `name`, `type`, `parent`, `remove()`, plugin data                                         |
      | `SceneNodeMixin`             | L5561 | `visible`, `locked`, `opacity`, variable bindings                                               |
      | `ChildrenMixin`              | L5773 | `children`, `appendChild()`, `insertChild()`, `findAll()`, `findOne()`, `findAllWithCriteria()` |
      | `LayoutMixin`                | L6135 | `x`, `y`, `width`, `height`, `rotation`, `resize()`, `rescale()`                                |
      | `AutoLayoutMixin`            | L6436 | `layoutMode`, axis alignment, padding, `itemSpacing`, `layoutSizingHorizontal/Vertical`         |
      | `AutoLayoutChildrenMixin`    | L7064 | `layoutAlign`, `layoutGrow`, sizing — **set AFTER `appendChild()`**                             |
      | `GridLayoutMixin`            | L6939 | CSS Grid tracks, gap, template                                                                  |
      | `GridChildrenMixin`          | L7127 | grid child positioning                                                                          |
      | `GeometryMixin`              | L7485 | `fills`, `strokes`, `strokeWeight`, `strokeAlign`                                               |
      | `MinimalFillsMixin`          | L7328 | `fills` only                                                                                    |
      | `MinimalStrokesMixin`        | L7246 | `strokes`, `strokeWeight`                                                                       |
      | `BlendMixin`                 | L6339 | `opacity`, `blendMode`, `isMask`, `effects`                                                     |
      | `CornerMixin`                | L7537 | `cornerRadius`, `cornerSmoothing`                                                               |
      | `RectangleCornerMixin`       | L7560 | Per-corner radii                                                                                |
      | `ExportMixin`                | L7577 | `exportSettings`, `exportAsync()`                                                               |
      | `ReactionMixin`              | L7704 | `reactions` (prototyping)                                                                       |
      | `PublishableMixin`           | L7875 | `description`, `key`, `getPublishStatusAsync()`                                                 |
      | `VariantMixin`               | L8182 | `variantProperties`                                                                             |
      | `ComponentPropertiesMixin`   | L8229 | `componentProperties`, `addComponentProperty()`                                                 |
      | `PluginDataMixin`            | L5443 | `getPluginData()`, `setPluginData()`, `getSharedPluginData()`                                   |
      | `FramePrototypingMixin`      | L7651 | `overflowDirection`, `numberOfFixedChildren`                                                    |
      | `BaseFrameMixin`             | L7939 | ChildrenMixin + LayoutMixin + AutoLayoutMixin + GeometryMixin + …                               |
      | `DefaultFrameMixin`          | L7997 | BaseFrameMixin + FramePrototypingMixin + ReactionMixin                                          |
      | `DefaultShapeMixin`          | L7928 | BlendMixin + GeometryMixin + LayoutMixin + ExportMixin + ReactionMixin                          |
      | `ExplicitVariableModesMixin` | L9084 | `setExplicitVariableModeForCollection()`                                                        |
      
      ---
      
      ## Paint & Fill (L4302)
      
      | Type            | L#    | Notes                                                                             |
      | --------------- | ----- | --------------------------------------------------------------------------------- |
      | `SolidPaint`    | L4302 | `type:'SOLID'`, `color: RGB`, `opacity`, `visible`, `blendMode`                   |
      | `GradientPaint` | L4357 | `type: 'GRADIENT_LINEAR\|RADIAL\|ANGULAR\|DIAMOND'`, `gradientStops: ColorStop[]` |
      | `ImagePaint`    | L4377 | `type:'IMAGE'`, `imageHash`, `scaleMode`                                          |
      | `VideoPaint`    | L4413 | `type:'VIDEO'`                                                                    |
      | `PatternPaint`  | L4449 | `type:'PATTERN'`                                                                  |
      | `type Paint`    | L4481 | Union of all five                                                                 |
      | `ColorStop`     | L4271 | `{ position: number, color: RGBA }`                                               |
      | `ImageFilters`  | L4290 | exposure, contrast, saturation, etc.                                              |
      
      > **CRITICAL**: Fills/strokes are **read-only arrays** — clone, modify, reassign.
      
      ---
      
      ## Effects (L3966)
      
      | Type                               | L#    |
      | ---------------------------------- | ----- |
      | `DropShadowEffect`                 | L3966 |
      | `InnerShadowEffect`                | L4009 |
      | `BlurEffect` (Normal/Progressive)  | L4048 |
      | `NoiseEffect` (Mono/Duo/Multitone) | L4105 |
      | `TextureEffect`                    | L4180 |
      | `GlassEffect`                      | L4209 |
      | `type Effect`                      | L4250 |
      
      ---
      
      ## Typography
      
      | Type                | L#    | Notes                                                                                  |
      | ------------------- | ----- | -------------------------------------------------------------------------------------- |
      | `FontName`          | L3697 | `{ family: string, style: string }`                                                    |
      | `TextNode`          | L9493 | `characters`, `textAlignHorizontal`, `fontSize`, `fontName`, `getStyledTextSegments()` |
      | `StyledTextSegment` | L4882 | Per-range text properties                                                              |
      | `LetterSpacing`     | L4826 | `{ value, unit: 'PIXELS'\|'PERCENT' }`                                                 |
      | `LineHeight`        | L4830 | `{ value, unit } \| { unit: 'AUTO' }`                                                  |
      | `TextCase`          | L3701 | `'ORIGINAL'\|'UPPER'\|'LOWER'\|'TITLE'\|'SMALL_CAPS'`                                  |
      | `TextDecoration`    | L3702 | `'NONE'\|'UNDERLINE'\|'STRIKETHROUGH'`                                                 |
      | `OpenTypeFeature`   | L3728 | Ligatures, numerals, etc.                                                              |
      
      ---
      
      ## Variables & Bindings
      
      | Type                          | L#     | Notes                                                         |
      | ----------------------------- | ------ | ------------------------------------------------------------- |
      | `Variable`                    | L10204 | Core variable object                                          |
      | `VariableCollection`          | L10418 | Collection of variables + modes                               |
      | `VariableAlias`               | L10172 | Reference to another variable                                 |
      | `VariableValue`               | L10176 | `boolean \| string \| number \| RGB \| RGBA \| VariableAlias` |
      | `VariableResolvedDataType`    | L10171 | `'BOOLEAN' \| 'COLOR' \| 'FLOAT' \| 'STRING'`                 |
      | `VariableDataType`            | L5023  | Includes `'VARIABLE_ALIAS' \| 'EXPRESSION'`                   |
      | `VariableScope`               | L10177 | Where variable can be applied                                 |
      | `CodeSyntaxPlatform`          | L10203 | `'WEB' \| 'ANDROID' \| 'iOS'`                                 |
      | `VariableBindableNodeField`   | L5712  | Node fields that accept variable binding                      |
      | `VariableBindableTextField`   | L5739  | Text-specific bindable fields                                 |
      | `VariableBindablePaintField`  | L5748  | `'color'`                                                     |
      | `VariableBindableEffectField` | L5751  | `'color'\|'radius'\|'spread'\|'offsetX'\|'offsetY'`           |
      
      ---
      
      ## Styles
      
      | Interface        | L#     | Notes                                                  |
      | ---------------- | ------ | ------------------------------------------------------ |
      | `BaseStyleMixin` | L10977 | `name`, `id`, `key`, `type`, `description`, `remove()` |
      | `PaintStyle`     | L11002 | `type:'PAINT'`, `paints: Paint[]`                      |
      | `TextStyle`      | L11018 | `type:'TEXT'`, font properties                         |
      | `EffectStyle`    | L11087 | `type:'EFFECT'`, `effects: Effect[]`                   |
      | `GridStyle`      | L11103 | `type:'GRID'`, `layoutGrids`                           |
      | `type BaseStyle` | L11119 | Union of all four                                      |
      | `type StyleType` | L10955 | `'PAINT' \| 'TEXT' \| 'EFFECT' \| 'GRID'`              |
      
      ---
      
      ## Primitives & Geometry
      
      | Type             | L#    | Shape                                         |
      | ---------------- | ----- | --------------------------------------------- |
      | `Vector`         | L3667 | `{ x: number, y: number }`                    |
      | `Rect`           | L3671 | `{ x, y, width, height }`                     |
      | `RGB`            | L3680 | `{ r, g, b }` — **0–1 range, not 0–255**      |
      | `RGBA`           | L3688 | `{ r, g, b, a }` — **0–1 range**              |
      | `Transform`      | L3666 | `[[a,b,tx],[c,d,ty]]` 2×3 affine matrix       |
      | `ArcData`        | L3958 | `{ startingAngle, endingAngle, innerRadius }` |
      | `Constraints`    | L4264 | `{ horizontal, vertical }: ConstraintType`    |
      | `ConstraintType` | L4260 | `'MIN'\|'CENTER'\|'MAX'\|'STRETCH'\|'SCALE'`  |
      | `VectorPath`     | L4792 | `{ windingRule, data: string }`               |
      | `VectorNetwork`  | L4775 | vertices + segments + regions                 |
      | `Guide`          | L4482 | `{ axis, offset }`                            |
      
      ---
      
      ## Prototyping
      
      | Type                  | L#    | Notes                                                     |
      | --------------------- | ----- | --------------------------------------------------------- |
      | `Reaction`            | L5015 | trigger + action pair                                     |
      | `Trigger`             | L5146 | what initiates the reaction                               |
      | `Action`              | L5064 | what happens                                              |
      | `Transition`          | L5145 | `SimpleTransition \| DirectionalTransition`               |
      | `Easing`              | L5182 | easing curve definition                                   |
      | `Navigation`          | L5178 | `'NAVIGATE'\|'SWAP'\|'OVERLAY'\|'SCROLL_TO'\|'CHANGE_TO'` |
      | `OverflowDirection`   | L5215 | `'NONE'\|'HORIZONTAL'\|'VERTICAL'\|'BOTH'`                |
      | `OverlayPositionType` | L5219 | overlay placement                                         |
      
      ---
      
      ## Events & Changes
      
      | Type                  | L#    | Notes                                                           |
      | --------------------- | ----- | --------------------------------------------------------------- |
      | `ArgFreeEventType`    | L11   | `'selectionchange'\|'currentpagechange'\|'close'\|timer events` |
      | `RunEvent`            | L3321 | plugin run with parameters                                      |
      | `DropEvent`           | L3339 | drag-and-drop                                                   |
      | `DocumentChangeEvent` | L3359 | any document change                                             |
      | `NodeChangeEvent`     | L3626 | node property changes                                           |
      | `NodeChangeProperty`  | L3499 | all watchable property names                                    |
      | `StyleChangeEvent`    | L3365 | style create/delete/update                                      |
      | `DocumentChange`      | L3489 | `CreateChange \| DeleteChange \| PropertyChange`                |
      | `TextReviewEvent`     | L3657 | text review mode                                                |
      
      ---
      
      ## Export
      
      | Type                        | L#    | Notes                                         |
      | --------------------------- | ----- | --------------------------------------------- |
      | `ExportSettingsImage`       | L4561 | PNG/JPG/WEBP/BMP                              |
      | `ExportSettingsSVG`         | L4634 |                                               |
      | `ExportSettingsPDF`         | L4653 |                                               |
      | `ExportSettingsREST`        | L4667 |                                               |
      | `ExportSettingsConstraints` | L4554 | `{ type: 'SCALE'\|'WIDTH'\|'HEIGHT', value }` |
      
      ---
      
      ## Key Sub-API Surfaces
      
      **ClientStorageAPI (L2531):** `getAsync(key)`, `setAsync(key, value)`, `keysAsync()`, `deleteAsync(key)`
      
      **ViewportAPI (L3086):** `center: Vector`, `zoom: number`, `scrollAndZoomIntoView(nodes)`, `bounds: Rect`
      
      **UtilAPI (L2691):** `solidPaint(hex, opacity?)`, `rgba(r,g,b,a?)`, `rgb(r,g,b)`, `colorToHex(color)`, `loadImageAsync(url)`, `clone(val)`
      
      **TeamLibraryAPI (L2372):** `getAvailableLibraryVariableCollectionsAsync()`, `importVariableByKeyAsync(key)`
      
      **Image (L11120):** `hash`, `getBytesAsync()`, `getSizeAsync()`
      
      ---
      
      ## All Symbols (flat — grep these against the .d.ts file)
      
      To find any symbol: `grep -n "^interface Foo\|^type Foo\|^declare type Foo" plugin-api-standalone.d.ts`
      
      ```
      PluginAPI               VariablesAPI            AnnotationsAPI          TeamLibraryAPI
      UIAPI                   UtilAPI                 ViewportAPI             ClientStorageAPI
      ConstantsAPI            CodegenAPI              PaymentsAPI             TextReviewAPI
      ParametersAPI           TimerAPI                BuzzAPI                 DevResourcesAPI
      
      DocumentNode            PageNode                FrameNode               GroupNode
      ComponentNode           ComponentSetNode        InstanceNode            RectangleNode
      EllipseNode             LineNode                PolygonNode             StarNode
      VectorNode              TextNode                TextPathNode            BooleanOperationNode
      SliceNode               SectionNode             TableNode               TableCellNode
      StickyNode              ConnectorNode           ShapeWithTextNode       StampNode
      CodeBlockNode           EmbedNode               LinkUnfurlNode          MediaNode
      WidgetNode              SlideNode               SlideRowNode            SlideGridNode
      TransformGroupNode      HighlightNode           WashiTapeNode
      
      BaseNodeMixin           SceneNodeMixin          ChildrenMixin           LayoutMixin
      AutoLayoutMixin         AutoLayoutChildrenMixin GridLayoutMixin         GridChildrenMixin
      GeometryMixin           MinimalFillsMixin       MinimalStrokesMixin     BlendMixin
      MinimalBlendMixin       CornerMixin             RectangleCornerMixin    ExportMixin
      ReactionMixin           PublishableMixin        VariantMixin            ComponentPropertiesMixin
      PluginDataMixin         DevResourcesMixin       DevStatusMixin          StickableMixin
      ConstraintMixin         DimensionAndPositionMixin AspectRatioLockMixin  FramePrototypingMixin
      BaseFrameMixin          DefaultFrameMixin       DefaultShapeMixin       OpaqueNodeMixin
      VectorLikeMixin         ComplexStrokesMixin     IndividualStrokesMixin  ContainerMixin
      AnnotationsMixin        MeasurementsMixin       ExplicitVariableModesMixin
      
      Variable                VariableCollection      VariableAlias           ExtendedVariableCollection
      LibraryVariableCollection LibraryVariable
      VariableValue           VariableResolvedDataType VariableDataType       VariableScope
      CodeSyntaxPlatform      VariableBindableNodeField VariableBindableTextField
      VariableBindablePaintField VariableBindableEffectField VariableBindableLayoutGridField
      
      SolidPaint              GradientPaint           ImagePaint              VideoPaint
      PatternPaint            Paint                   ColorStop               ImageFilters
      DropShadowEffect        InnerShadowEffect       BlurEffect              NoiseEffect
      TextureEffect           GlassEffect             Effect
      LayoutGrid              RowsColsLayoutGrid      GridLayoutGrid
      
      PaintStyle              TextStyle               EffectStyle             GridStyle
      BaseStyle               BaseStyleMixin          StyleType
      
      FontName                Font                    LetterSpacing           LineHeight
      TextCase                TextDecoration          TextDecorationStyle     FontStyle
      OpenTypeFeature         StyledTextSegment       LeadingTrim
      
      Vector                  Rect                    RGB                     RGBA
      Transform               ArcData                 Constraints             ConstraintType
      VectorPath              VectorNetwork           VectorVertex            VectorSegment
      VectorRegion            Guide                   BlendMode               MaskType
      
      Reaction                Trigger                 Action                  Transition
      Easing                  Navigation              OverflowDirection       OverlayPositionType
      OverlayBackground       PublishStatus
      
      ArgFreeEventType        RunEvent                DropEvent               DocumentChangeEvent
      NodeChangeEvent         NodeChangeProperty      StyleChangeEvent        DocumentChange
      TextReviewEvent         SlidesViewChangeEvent   CanvasViewChangeEvent
      
      ExportSettingsImage     ExportSettingsSVG       ExportSettingsPDF       ExportSettingsREST
      ExportSettingsConstraints
      
      User                    ActiveUser              BaseUser                Image
      Video                   VersionHistoryResult    FindAllCriteria
      ```
      
    • text-style-patterns.md 6.4 KB
      # Text Style API Patterns
      
      > Part of the [use_figma skill](../SKILL.md). How to create, apply, and inspect text styles using the Plugin API.
      >
      > For design system context (when to create text styles, how they relate to tokens, headless limitations), see [wwds-text-styles](working-with-design-systems/wwds-text-styles.md).
      
      ## Contents
      
      - Listing Text Styles
      - Creating a Text Style
      - Probing Font Styles
      - Creating a Type Ramp (Multi-Step)
      - Applying Text Styles to Nodes
      
      ## Listing Text Styles
      
      ```javascript
      /**
       * Lists all local text styles with their key properties.
       *
       * @returns {Promise<Array<{id: string, name: string, key: string, fontSize: number, fontName: FontName, lineHeight: LineHeight, letterSpacing: LetterSpacing}>>}
       */
      async function listTextStyles() {
        const styles = await figma.getLocalTextStylesAsync();
        return styles.map(s => ({
          id: s.id,
          name: s.name,
          key: s.key,
          fontSize: s.fontSize,
          fontName: s.fontName,
          lineHeight: s.lineHeight,
          letterSpacing: s.letterSpacing
        }));
      }
      ```
      
      Full runnable script:
      
      ```javascript
      (async () => {
        try {
          const results = await listTextStyles();
          figma.closePlugin(JSON.stringify(results));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ## Creating a Text Style
      
      Font **MUST** be loaded before setting `fontName`. `lineHeight` and `letterSpacing` must be `{value, unit}` objects — bare numbers throw.
      
      ```javascript
      /**
       * Creates a text style with all typographic properties set.
       * Font MUST be loaded before calling.
       *
       * @param {string} name - Slash-delimited name, e.g. "body/base"
       * @param {{ family: string, style: string }} fontName
       * @param {number} fontSize - In pixels
       * @param {{ value: number, unit: 'PIXELS' | 'PERCENT' } | { unit: 'AUTO' }} lineHeight
       * @param {{ value: number, unit: 'PIXELS' | 'PERCENT' }} [letterSpacing]
       * @param {string} [description] - e.g. the CSS variable name "CSS: var(--font-body-base)"
       * @returns {TextStyle}
       */
      function createTextStyleFull(name, fontName, fontSize, lineHeight, letterSpacing, description) {
        const style = figma.createTextStyle();
        style.name = name;
        style.fontName = fontName;
        style.fontSize = fontSize;
        style.lineHeight = lineHeight; // { unit: 'AUTO' } | { value, unit: 'PIXELS'|'PERCENT' }
        if (letterSpacing) style.letterSpacing = letterSpacing;
        if (description) style.description = description;
        return style;
      }
      ```
      
      ## Probing Font Styles
      
      Font style names vary per provider and per file (`"SemiBold"` vs `"Semi Bold"`). Always probe before hardcoding:
      
      ```javascript
      /**
       * Probes available font styles for a given family.
       * Useful when font style names are unknown (e.g. "SemiBold" vs "Semi Bold").
       *
       * @param {string} family - Font family name, e.g. "Inter"
       * @param {string[]} stylesToTest - Candidate style names to probe
       * @returns {Promise<string[]>} - Style names that loaded successfully
       */
      async function probeAvailableFontStyles(family, stylesToTest) {
        const available = [];
        for (const style of stylesToTest) {
          try {
            await figma.loadFontAsync({ family, style });
            available.push(style);
          } catch (_) {}
        }
        return available;
      }
      ```
      
      ## Creating a Type Ramp (Multi-Step)
      
      Handles font loading, deduplication, and idempotency. Each entry: `[name, fontFamily, fontStyle, fontSize_px, lineHeight, cssVar]`.
      
      **HEADLESS NOTE:** `setBoundVariable` on `TextStyle` is not supported in `use_figma`. This function sets raw values. To bind variables, do it interactively in Figma after creation.
      
      ```javascript
      /**
       * Creates a full type ramp from a token definition array.
       * Handles font loading, deduplication, and idempotency.
       *
       * Each entry: [name, fontFamily, fontStyle, fontSize_px, lineHeight, cssVar]
       *   - lineHeight: { unit: 'AUTO' } or { value: number, unit: 'PIXELS' | 'PERCENT' }
       *
       * @param {Array} defs - Array of [name, fontFamily, fontStyle, fontSize, lineHeight, cssVar] tuples
       * @returns {Promise<{ created: string[], skipped: string[] }>}
       */
      async function createTypeRamp(defs) {
        const uniqueFonts = new Set();
        for (const [, family, style] of defs) {
          uniqueFonts.add(JSON.stringify({ family, style }));
        }
        await Promise.all(
          [...uniqueFonts].map(f => figma.loadFontAsync(JSON.parse(f)))
        );
      
        const existing = new Set(
          (await figma.getLocalTextStylesAsync()).map(s => s.name)
        );
      
        const created = [];
        const skipped = [];
      
        for (const [name, family, style, fontSize, lineHeight, cssVar] of defs) {
          if (existing.has(name)) {
            skipped.push(name);
            continue;
          }
          const ts = figma.createTextStyle();
          ts.name = name;
          ts.fontName = { family, style };
          ts.fontSize = fontSize;
          ts.lineHeight = lineHeight ?? { unit: 'AUTO' };
          if (cssVar) ts.description = `CSS: var(${cssVar})`;
          created.push(name);
        }
      
        return { created, skipped };
      }
      ```
      
      Full runnable script:
      
      ```javascript
      (async () => {
        try {
          const defs = [
            ['heading/xl', 'Inter', 'Bold',      48, { unit: 'PIXELS', value: 56 }, '--font-heading-xl'],
            ['heading/lg', 'Inter', 'Bold',      36, { unit: 'PIXELS', value: 44 }, '--font-heading-lg'],
            ['body/base',  'Inter', 'Regular',   16, { unit: 'AUTO' },              '--font-body-base'],
            ['body/sm',    'Inter', 'Regular',   14, { unit: 'AUTO' },              '--font-body-sm'],
            ['code/base',  'Roboto Mono', 'Regular', 14, { unit: 'AUTO' },          '--font-code-base'],
          ];
          const result = await createTypeRamp(defs);
          figma.closePlugin(JSON.stringify(result));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ## Applying Text Styles to Nodes
      
      ```javascript
      /**
       * Applies a text style to all TEXT nodes on the current page that match a given name pattern.
       *
       * @param {string} styleId - The ID of a TextStyle.
       * @param {string} nodeNamePattern - Substring match against node names.
       * @returns {Promise<number>} - Number of nodes the style was applied to.
       */
      async function applyTextStyleToMatchingNodes(styleId, nodeNamePattern) {
        const textNodes = figma.currentPage.findAllWithCriteria({ types: ['TEXT'] });
        let applied = 0;
        for (const node of textNodes) {
          if (node.name.includes(nodeNamePattern)) {
            await node.setTextStyleIdAsync(styleId);
            applied++;
          }
        }
        return applied;
      }
      ```
      
      Full runnable script:
      
      ```javascript
      (async () => {
        try {
          const applied = await applyTextStyleToMatchingNodes('STYLE_ID', 'Heading');
          figma.closePlugin(JSON.stringify({ applied }));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
    • validation-and-recovery.md 5.6 KB
      # Validation Workflow & Error Recovery
      
      > Part of the [use_figma skill](../SKILL.md). How to debug, validate, and recover from errors.
      
      ## Contents
      
      - `get_metadata` vs `get_screenshot`
      - Error Recovery After Failed `use_figma`
      - Cleanup Pattern
      - Recommended Workflow
      
      
      ## `get_metadata` vs `get_screenshot`
      
      After each `use_figma` call, validate results using the right tool for the job. Do NOT reach for `get_screenshot` every time — it is expensive and should be reserved for visual checks.
      
      ### `get_metadata` — Use for intermediate validation (preferred)
      
      `get_metadata` returns an XML tree of node IDs, types, names, positions, and sizes. Use it to confirm:
      
      - **Structure & hierarchy**: correct parent-child relationships, component nesting, section contents
      - **Node counts**: expected number of variants created, children present
      - **Naming**: variant property names follow the `property=value` convention
      - **Positioning & alignment**: x/y coordinates, width/height values match expectations
      - **Layout properties**: auto-layout direction, sizing mode, padding, spacing
      - **Component set membership**: all expected variants are inside the ComponentSet
      
      ```
      Example: After creating a ComponentSet with 120 variants, call get_metadata on the
      ComponentSet node to verify all 120 children exist with correct names, sizes, and positions
      — without waiting for a full render.
      ```
      
      **When to use `get_metadata`:**
      - After creating/modifying nodes — to verify structure, counts, and names
      - After layout operations — to verify positions and dimensions
      - After combining variants — to confirm all components are in the ComponentSet
      - After binding variables — to verify node properties (use use_figma to read bound variables if needed)
      - Between multi-step workflows — to confirm step N succeeded before starting step N+1
      
      ### `get_screenshot` — Use after each major creation milestone
      
      `get_screenshot` renders a pixel-accurate image. It is the only way to verify visual correctness (colors, typography rendering, effects, variable mode resolution). It is slower and produces large responses, so don't call it after every single `use_figma` — but do call it after each major milestone to catch visual problems early.
      
      **When to use `get_screenshot`:**
      - **After creating a component set** — verify variants look correct, grid is readable, nothing is collapsed or overlapping
      - **After composing a layout** — verify overall structure and spacing
      - **After binding variables/modes** — verify colors and tokens resolved correctly
      - **After any fix or recovery** — verify the fix didn't introduce new visual issues
      - **Before reporting results to the user** — final visual proof
      
      **What to look for in screenshots** — these are the most commonly missed issues:
      - **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") instead of actual content
      
      ## CRITICAL: Error Recovery After Failed `use_figma`
      
      > **THIS IS NOT OPTIONAL.** Every `use_figma` error MUST trigger the recovery steps below. Skipping these steps leaves orphaned nodes in the file that will cause duplicates and inconsistencies on retry.
      
      **Scripts can partially execute before hitting an error.** A failed `use_figma` does NOT roll back — nodes created before the error line persist in the file. This leaves the file in an **inconsistent, partially-modified state**.
      
      **Mandatory recovery steps when `use_figma` returns an error (DO NOT SKIP):**
      1. **STOP — do NOT immediately fix the code and retry.** The file has partial state that must be inspected first.
      2. **Immediately call `get_metadata`** on the parent node (section, page, or ComponentSet) to see what was partially created.
      3. **If `get_metadata` doesn't make the damage clear** (e.g. positions look fine but visual state is uncertain), call `get_screenshot` to assess visual damage.
      4. **Write a cleanup script** to remove orphaned/incomplete nodes before retrying. Use `page.findChildren()` to locate stray nodes.
      5. **Only after cleanup is confirmed**, fix the original script and retry.
      6. **Never retry the failed script blindly** — the partial state means a retry will create duplicates or hit new errors.
      
      ```
      Example: A script creating 8 components fails on component #5.
      Components 1-4 exist on the page. A naive retry creates components 1-8 again,
      leaving 12 components total (4 orphaned duplicates). Always clean up first.
      ```
      
      ### Cleanup Pattern
      
      ```js
      // Cleanup pattern: find and remove orphaned nodes from a failed run
      (async () => {
        try {
          const page = figma.currentPage;
          // Find orphaned components that weren't combined into a ComponentSet
          const orphans = page.findChildren(n =>
            n.type === 'COMPONENT' && n.name.includes('variant=')
          );
          for (const orphan of orphans) orphan.remove();
          figma.closePlugin('Cleaned up ' + orphans.length + ' orphaned nodes');
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ## Recommended Workflow
      
      ```
      1. use_figma  →  Create/modify nodes
      2. get_metadata     →  Verify structure, counts, names, positions (fast, cheap)
      3. use_figma  →  Fix any structural issues found
      4. get_metadata     →  Re-verify fixes
      5. ... repeat as needed ...
      6. get_screenshot   →  Visual check after each major milestone
      
      ⚠️ ON ERROR at any step:
         a. get_metadata    →  Inspect partial state (always do this first)
         b. get_screenshot  →  Only if metadata doesn't make the damage clear
         c. use_figma →  Clean up orphaned/incomplete nodes
         d. THEN retry the failed operation
      ```
      
    • variable-patterns.md 12 KB
      # Variable & Token API Patterns
      
      > Part of the [use_figma skill](../SKILL.md). How to correctly create, bind, scope, and alias variables using the Plugin API.
      >
      > For design system context (aliasing strategy, mode decisions, code syntax philosophy, grouping conventions), see [wwds-variables](working-with-design-systems/wwds-variables.md).
      
      ## Contents
      
      - Creating Variable Collections and Modes
      - Creating Variables (All Types)
      - Binding Variables to Node Properties
      - Variable Scopes: What They Are and How to Set Them
      - Variable Aliasing (VARIABLE_ALIAS)
      - Code Syntax (setVariableCodeSyntax)
      - Discovering Existing Variables in the File
      - Effect Styles (For Shadows)
      
      
      ## Creating Variable Collections and Modes
      
      ```javascript
      const collection = figma.variables.createVariableCollection("MyCollection");
      
      // A new collection starts with 1 mode named "Mode 1" — always rename it
      collection.renameMode(collection.modes[0].modeId, "Light");
      
      // Add additional modes (returns the new modeId)
      const darkModeId = collection.addMode("Dark");
      const lightModeId = collection.modes[0].modeId;
      ```
      
      **Mode limits are plan-dependent:** Free = 1 mode, Professional = up to 4, Organization/Enterprise = 40+. If you need many modes, split across multiple collections.
      
      ## Creating Variables (All Types)
      
      `figma.variables.createVariable(name, collection, resolvedType)` — the second argument accepts a collection object or ID string (object preferred).
      
      ```javascript
      // COLOR — values use {r, g, b, a} (all 0–1 range, includes alpha)
      const colorVar = figma.variables.createVariable("my-color", collection, "COLOR");
      colorVar.setValueForMode(modeId, { r: 0.2, g: 0.36, b: 0.96, a: 1 });
      
      // FLOAT — for spacing, radii, sizing, numeric values
      const floatVar = figma.variables.createVariable("my-spacing", collection, "FLOAT");
      floatVar.setValueForMode(modeId, 16);
      
      // STRING — for font families, font style names, any text value
      const stringVar = figma.variables.createVariable("my-font", collection, "STRING");
      stringVar.setValueForMode(modeId, "Inter");
      
      // BOOLEAN
      const boolVar = figma.variables.createVariable("my-flag", collection, "BOOLEAN");
      boolVar.setValueForMode(modeId, true);
      ```
      
      **Note:** Paint colors use `{r, g, b}` (no alpha), but COLOR variable values use `{r, g, b, a}` (with alpha). Don't mix them up.
      
      ## Binding Variables to Node Properties
      
      ### Color Bindings (Fills, Strokes)
      
      `setBoundVariableForPaint` returns a **NEW paint** — you must capture the return value:
      
      ```javascript
      // Create a base paint, bind the variable, assign the result
      const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } };
      const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", colorVar);
      node.fills = [boundPaint];
      
      // Only SOLID paints support color variable binding — gradients/images will throw
      ```
      
      ### Numeric Bindings (Spacing, Radii, Sizing)
      
      `setBoundVariable` binds FLOAT/STRING/BOOLEAN variables to node properties:
      
      ```javascript
      // Padding
      node.setBoundVariable("paddingTop", spacingVar);
      node.setBoundVariable("paddingBottom", spacingVar);
      node.setBoundVariable("paddingLeft", spacingVar);
      node.setBoundVariable("paddingRight", spacingVar);
      
      // Gap
      node.setBoundVariable("itemSpacing", gapVar);
      node.setBoundVariable("counterAxisSpacing", gapVar);
      
      // Corner radius — use individual corners, NOT cornerRadius
      node.setBoundVariable("topLeftRadius", radiusVar);
      node.setBoundVariable("topRightRadius", radiusVar);
      node.setBoundVariable("bottomLeftRadius", radiusVar);
      node.setBoundVariable("bottomRightRadius", radiusVar);
      
      // Size
      node.setBoundVariable("width", sizeVar);
      node.setBoundVariable("height", sizeVar);
      node.setBoundVariable("minWidth", sizeVar);
      node.setBoundVariable("maxWidth", sizeVar);
      
      // Other
      node.setBoundVariable("opacity", opacityVar);
      node.setBoundVariable("strokeWeight", strokeVar);
      ```
      
      **Not bindable via setBoundVariable:** `fontSize`, `fontWeight`, `lineHeight` — set these directly on text nodes.
      
      ### Effect Bindings
      
      ```javascript
      const effectCopy = JSON.parse(JSON.stringify(node.effects[0]));
      const newEffect = figma.variables.setBoundVariableForEffect(effectCopy, "color", colorVar);
      // ⚠️ Returns a NEW effect — must capture return value!
      node.effects = [newEffect];
      // Valid fields: "color" (COLOR), "radius" | "spread" | "offsetX" | "offsetY" (FLOAT)
      ```
      
      ### Applying a Mode to a Frame
      
      ```javascript
      // All bound children of this frame will resolve to the specified mode's values
      frame.setExplicitVariableModeForCollection(collection.id, modeId);
      ```
      
      Without this, all nodes use the collection's default (first) mode.
      
      ## Variable Scopes: What They Are and How to Set Them
      
      `variable.scopes` controls which Figma property pickers show the variable. The default is `["ALL_SCOPES"]` which shows it everywhere — this is almost never what you want.
      
      ```javascript
      variable.scopes = ["FRAME_FILL", "SHAPE_FILL"];  // only fill pickers
      variable.scopes = ["TEXT_FILL"];                   // only text color picker
      variable.scopes = ["GAP"];                         // only gap/spacing pickers
      variable.scopes = ["CORNER_RADIUS"];               // only radius pickers
      variable.scopes = [];                              // hidden from all pickers
      ```
      
      **All valid scope values:**
      `ALL_SCOPES`, `TEXT_CONTENT`, `CORNER_RADIUS`, `WIDTH_HEIGHT`, `GAP`, `ALL_FILLS`, `FRAME_FILL`, `SHAPE_FILL`, `TEXT_FILL`, `STROKE_COLOR`, `STROKE_FLOAT`, `EFFECT_FLOAT`, `EFFECT_COLOR`, `OPACITY`, `FONT_FAMILY`, `FONT_STYLE`, `FONT_WEIGHT`, `FONT_SIZE`, `LINE_HEIGHT`, `LETTER_SPACING`, `PARAGRAPH_SPACING`, `PARAGRAPH_INDENT`
      
      **Always check the existing file's scope patterns before creating variables** — match whatever convention is already in use. See "Discovering Existing Variables" below.
      
      ## Variable Aliasing (VARIABLE_ALIAS)
      
      A variable's value can reference another variable via alias. This is how semantic tokens reference primitive tokens:
      
      ```javascript
      // Set a variable's value as an alias to another variable
      semanticVar.setValueForMode(modeId, {
        type: 'VARIABLE_ALIAS',
        id: primitiveVar.id
      });
      ```
      
      When the primitive changes, the semantic variable updates automatically across all modes.
      
      ## Code Syntax (setVariableCodeSyntax)
      
      Links a Figma variable back to its code counterpart. Call once per platform:
      
      ```javascript
      variable.setVariableCodeSyntax('WEB', 'var(--color-bg-default)');
      variable.setVariableCodeSyntax('ANDROID', 'colorBgDefault');
      variable.setVariableCodeSyntax('iOS', 'Color.bgDefault');
      
      // Read back: variable.codeSyntax → { WEB: '...', ANDROID: '...', iOS: '...' }
      ```
      
      **When deriving CSS names from Figma names**, replace both slashes AND spaces with hyphens:
      
      ```javascript
      // WRONG — leaves spaces in CSS variable name
      `var(--${figmaName.replace(/\//g, '-').toLowerCase()})`
      
      // CORRECT — replace all whitespace and slashes
      `var(--${figmaName.replace(/[\s\/]+/g, '-').toLowerCase()})`
      
      // BEST — use the original CSS variable name from the source, not a derived one
      `var(${token.cssVar})`
      ```
      
      ## Discovering Existing Variables in the File
      
      **Always inspect the file's existing variables before creating new ones.** Different files use different naming conventions, scope patterns, and collection structures. Match what's already there.
      
      ### List collections with mode info
      
      ```javascript
      (async () => {
        try {
          const collections = figma.variables.getLocalVariableCollections();
          const results = collections.map(c => ({
            name: c.name,
            id: c.id,
            varCount: c.variableIds.length,
            modes: c.modes.map(m => ({ name: m.name, id: m.modeId }))
          }));
          figma.closePlugin(JSON.stringify(results));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ### Inspect scope patterns used in existing variables
      
      ```javascript
      (async () => {
        try {
          const collections = figma.variables.getLocalVariableCollections();
          const scopeGroups = {};
          for (const c of collections) {
            for (const id of c.variableIds) {
              const v = figma.variables.getVariableById(id);
              const key = JSON.stringify(v.scopes);
              if (!scopeGroups[key]) scopeGroups[key] = [];
              scopeGroups[key].push(v.name);
            }
          }
          figma.closePlugin(JSON.stringify(scopeGroups));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ### Build a name→variable lookup for reuse
      
      ```javascript
      const varByName = {};
      for (const v of figma.variables.getLocalVariables()) {
        varByName[v.name] = v;
      }
      
      // Bind to existing variable by name — no hex values needed
      function bindFill(node, varName) {
        const v = varByName[varName];
        if (!v) throw new Error(`Variable not found: ${varName}`);
        const paint = figma.variables.setBoundVariableForPaint(
          { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', v
        );
        node.fills = [paint];
      }
      ```
      
      **Only create new variables for tokens that have no match in the file.** After building the lookup, compare against the needed tokens and create variables only for the delta.
      
      ## Listing Collections with Full Variable Details
      
      The async API returns richer data including code syntax and scopes per variable:
      
      ```javascript
      /**
       * Lists all local variable collections defined in the current Figma file,
       * including metadata for their modes and variables.
       *
       * @returns {Promise<Array<{
       *   name: string,
       *   id: string,
       *   modes: Array<[name: string, modeId: string]>,
       *   variables: Array<[name: string, id: string, codeSyntax: object, scopes: string[]]>
       * }>>}
       */
      async function listVariableCollectionsAndVariables() {
        const collections = await figma.variables.getLocalVariableCollectionsAsync();
        const results = [];
        for (const collection of collections) {
          const vars = [];
          for (const id of collection.variableIds) {
            const v = await figma.variables.getVariableByIdAsync(id);
            vars.push([v.name, v.id, v.codeSyntax, v.scopes]);
          }
          results.push({
            name: collection.name,
            id: collection.id,
            modes: collection.modes.map(m => [m.name, m.modeId]),
            variables: vars
          });
        }
        return results;
      }
      ```
      
      Full runnable script:
      
      ```javascript
      (async () => {
        try {
          const results = await listVariableCollectionsAndVariables();
          figma.closePlugin(JSON.stringify(results));
        } catch(e) { figma.closePluginWithFailure(e.toString()); }
      })()
      ```
      
      ## Setting and Removing Code Syntax
      
      Must be executed in the file the variable is defined in:
      
      ```javascript
      /**
       * Set the code syntax for a variable for a specific platform.
       *
       * @param {string} variableId
       * @param {'WEB'|'ANDROID'|'iOS'} platform
       * @param {string} syntax
       */
      async function setVariableCodeSyntax(variableId, platform, syntax) {
        const variable = await figma.variables.getVariableByIdAsync(variableId);
        variable.setVariableCodeSyntax(platform, syntax);
      }
      
      /**
       * Remove code syntax for a variable for one or more platforms.
       *
       * @param {string} variableId
       * @param {Array<'WEB'|'ANDROID'|'iOS'>} platforms — defaults to all three
       */
      async function removeVariableCodeSyntax(variableId, platforms = ["WEB", "ANDROID", "iOS"]) {
        const variable = await figma.variables.getVariableByIdAsync(variableId);
        for (const platform of platforms) {
          variable.removeVariableCodeSyntax(platform);
        }
      }
      
      /**
       * Set a value for a variable in a specific mode.
       * For aliases, value must be: { type: 'VARIABLE_ALIAS', id: '<variableId>' }
       *
       * @param {string} variableId
       * @param {string} modeId
       * @param {string|number|boolean|RGB|RGBA|{type: 'VARIABLE_ALIAS', id: string}} value
       */
      async function setVariableValueForMode(variableId, modeId, value) {
        const variable = await figma.variables.getVariableByIdAsync(variableId);
        variable.setValueForMode(modeId, value);
      }
      ```
      
      ## Effect Styles (For Shadows)
      
      Shadows can't be stored as variables. Use effect styles. For comprehensive patterns, see [effect-style-patterns.md](effect-style-patterns.md).
      
      ```javascript
      const shadow = figma.createEffectStyle();
      shadow.name = "Shadow/Subtle";
      shadow.effects = [{
        type: "DROP_SHADOW",
        color: { r: 0, g: 0, b: 0, a: 0.06 },
        offset: { x: 0, y: 2 },
        radius: 8,
        spread: 0,
        visible: true,
        blendMode: "NORMAL"
      }];
      
      // Apply to a node
      frame.effectStyleId = shadow.id;
      ```
      
  • LICENSE.TXT 440 B
    Use of these Figma skills and related files ("Materials") is governed by the Figma Developer Terms (available at https://www.figma.com/legal/developer-terms/). By accessing, downloading, or using these Materials — including through automated systems or AI agents — you agree to the Figma Developer Terms.
    These Materials are currently offered as a Beta feature. Figma may modify, suspend, or discontinue them at any time without notice.
  • maintainers.yml 21 B
    SKILL.md: mcp_server
    
  • SKILL.md 17.4 KB
    ---
    name: figma-use
    description: "**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_figma — Figma Plugin API Skill
    
    Use `use_figma` MCP to execute JavaScript in Figma files via the Plugin API. All detailed reference docs live in `references/`.
    
    **Always pass `skillNames: "figma-use"` when calling `use_figma`.** This is a logging parameter used to track skill usage — it does not affect execution.
    
    **If the task involves building or updating a full page, screen, or multi-section layout in Figma from code**, also load [figma-generate-design](../figma-generate-design/SKILL.md). It provides the workflow for discovering design system components via `search_design_system`, importing them, and assembling screens incrementally. Both skills work together: this one for the API rules, that one for the screen-building workflow.
    
    Before anything, load [plugin-api-standalone.index.md](references/plugin-api-standalone.index.md) to understand what is possible. When you are asked to write plugin API code, use this context to grep [plugin-api-standalone.d.ts](references/plugin-api-standalone.d.ts) for relevant types, methods, and properties. This is the definitive source of truth for the API surface. It is a large typings file, so do not load it all at once, grep for relevant sections as needed.
    
    IMPORTANT: Whenever you work with design systems, start with [working-with-design-systems/wwds.md](references/working-with-design-systems/wwds.md) to understand the key concepts, processes, and guidelines for working with design systems in Figma. Then load the more specific references for components, variables, text styles, and effect styles as needed.
    
    ## 1. Critical Rules
    
    1.  **Use `return` to send data back.** The return value is JSON-serialized automatically (objects, arrays, strings, numbers). Do NOT call `figma.closePlugin()` or wrap code in an async IIFE — this is handled for you.
    2.  **Write plain JavaScript with top-level `await` and `return`.** Code is automatically wrapped in an async context. Do NOT wrap in `(async () => { ... })()`.
    3.  `figma.notify()` **throws "not implemented"** — never use it
    3a. `getPluginData()` / `setPluginData()` are **not supported** in `use_figma` — do not use them. Use `getSharedPluginData()` / `setSharedPluginData()` instead (these ARE supported), or track node IDs by returning them and passing them to subsequent calls.
    4.  `console.log()` is NOT returned — use `return` for output
    5.  **Work incrementally in small steps.** Break large operations into multiple `use_figma` calls. Validate after each step. This is the single most important practice for avoiding bugs.
    6.  Colors are **0–1 range** (not 0–255): `{r: 1, g: 0, b: 0}` = red
    7.  Fills/strokes are **read-only arrays** — clone, modify, reassign
    8.  Font **MUST** be loaded before any text operation: `await figma.loadFontAsync({family, style})`
    9.  **Pages load incrementally** — use `await figma.setCurrentPageAsync(page)` to switch pages and load their content (see Page Rules below)
    10. `setBoundVariableForPaint` returns a **NEW** paint — must capture and reassign
    11. `createVariable` accepts collection **object or ID string** (object preferred)
    12. **`layoutSizingHorizontal/Vertical = 'FILL'` MUST be set AFTER `parent.appendChild(child)`** — setting before append throws. Same applies to `'HUG'` on non-auto-layout nodes.
    13. **Position new top-level nodes away from (0,0).** Nodes appended directly to the page default to (0,0). Scan `figma.currentPage.children` to find a clear position (e.g., to the right of the rightmost node). This only applies to page-level nodes — nodes nested inside other frames or auto-layout containers are positioned by their parent. See [Gotchas](references/gotchas.md).
    14. **On `use_figma` error, STOP. Do NOT immediately retry.** Failed scripts are **atomic** — if a script errors, it is not executed at all and no changes are made to the file. Read the error message carefully, fix the script, then retry. See [Error Recovery](#6-error-recovery--self-correction).
    15. **MUST `return` ALL created/mutated node IDs.** Whenever a script creates new nodes or mutates existing ones on the canvas, collect every affected node ID and return them in a structured object (e.g. `return { createdNodeIds: [...], mutatedNodeIds: [...] }`). This is essential for subsequent calls to reference, validate, or clean up those nodes.
    16. **Always set `variable.scopes` explicitly when creating variables.** The default `ALL_SCOPES` pollutes every property picker — almost never what you want. Use specific scopes like `["FRAME_FILL", "SHAPE_FILL"]` for backgrounds, `["TEXT_FILL"]` for text colors, `["GAP"]` for spacing, etc. See [variable-patterns.md](references/variable-patterns.md) for the full list.
    17. **`await` every Promise.** Never leave a Promise unawaited — unawaited async calls (e.g. `figma.loadFontAsync(...)` without `await`, or `figma.setCurrentPageAsync(page)` without `await`) will fire-and-forget, causing silent failures or race conditions. The script may return before the async operation completes, leading to missing data or half-applied changes.
    
    > For detailed WRONG/CORRECT examples of each rule, see [Gotchas & Common Mistakes](references/gotchas.md).
    
    ## 2. Page Rules (Critical)
    
    **Page context resets between `use_figma` calls** — `figma.currentPage` starts on the first page each time.
    
    ### Switching pages
    
    Use `await figma.setCurrentPageAsync(page)` to switch pages and load their content. The sync setter `figma.currentPage = page` **throws an error** in `use_figma` runtimes.
    
    ```js
    // Switch to a specific page (loads its content)
    const targetPage = figma.root.children.find((p) => p.name === "My Page");
    await figma.setCurrentPageAsync(targetPage);
    // targetPage.children is now populated
    
    // Iterate over all pages
    for (const page of figma.root.children) {
      await figma.setCurrentPageAsync(page);
      // page.children is now loaded — read or modify them here
    }
    ```
    
    ### Across script runs
    
    `figma.currentPage` resets to the **first page** at the start of each `use_figma` call. If your workflow spans multiple calls and targets a non-default page, call `await figma.setCurrentPageAsync(page)` at the start of each invocation.
    
    You can call `use_figma` multiple times to incrementally build on the file state, or to retrieve information before writing another script. For example, write a script to get metadata about existing nodes, `return` that data, then use it in a subsequent script to modify those nodes.
    
    ## 3. `return` Is Your Output Channel
    
    The agent sees **ONLY** the value you `return`. Everything else is invisible.
    
    - **Returning IDs (CRITICAL)**: Every script that creates or mutates canvas nodes **MUST** return all affected node IDs — e.g. `return { createdNodeIds: [...], mutatedNodeIds: [...] }`. This is a hard requirement, not optional.
    - **Progress reporting**: `return { createdNodeIds: [...], count: 5, errors: [] }`
    - **Error info**: Thrown errors are automatically captured and returned — just let them propagate or `throw` explicitly.
    - `console.log()` output is **never** returned to the agent
    - Always return actionable data (IDs, counts, status) so subsequent calls can reference created objects
    
    ## 4. Editor Mode
    
    `use_figma` works in **design mode** (editorType `"figma"`, the default). FigJam (`"figjam"`) has a different set of available node types — most design nodes are blocked there.
    
    Available in design mode: Rectangle, Frame, Component, Text, Ellipse, Star, Line, Vector, Polygon, BooleanOperation, Slice, Page, Section, TextPath.
    
    **Blocked** in design mode: Sticky, Connector, ShapeWithText, CodeBlock, Slide, SlideRow, Webpage.
    
    ## 5. Incremental Workflow (How to Avoid Bugs)
    
    The most common cause of bugs is trying to do too much in a single `use_figma` call. **Work in small steps and validate after each one.**
    
    ### The pattern
    
    1. **Inspect first.** Before creating anything, run a read-only `use_figma` to discover what already exists in the file — pages, components, variables, naming conventions. Match what's there.
    2. **Do one thing per call.** Create variables in one call, create components in the next, compose layouts in another. Don't try to build an entire screen in one script.
    3. **Return IDs from every call.** Always `return` created node IDs, variable IDs, collection IDs as objects (e.g. `return { createdNodeIds: [...] }`). You'll need these as inputs to subsequent calls.
    4. **Validate after each step.** Use `get_metadata` to verify structure (counts, names, hierarchy, positions). Use `get_screenshot` after major milestones to catch visual issues.
    5. **Fix before moving on.** If validation reveals a problem, fix it before proceeding to the next step. Don't build on a broken foundation.
    
    ### Suggested step order for complex tasks
    
    ```
    Step 1: Inspect file — discover existing pages, components, variables, conventions
    Step 2: Create tokens/variables (if needed)
           → validate with get_metadata
    Step 3: Create individual components
           → validate with get_metadata + get_screenshot
    Step 4: Compose layouts from component instances
           → validate with get_screenshot
    Step 5: Final verification
    ```
    
    ### What to validate at each step
    
    | After... | Check with `get_metadata` | Check with `get_screenshot` |
    |---|---|---|
    | Creating variables | Collection count, variable count, mode names | — |
    | Creating components | Child count, variant names, property definitions | Variants visible, not collapsed, grid readable |
    | Binding variables | Node properties reflect bindings | Colors/tokens resolved correctly |
    | Composing layouts | Instance nodes have mainComponent, hierarchy correct | No cropped/clipped text, no overlapping elements, correct spacing |
    
    ## 6. Error Recovery & Self-Correction
    
    **`use_figma` is atomic — failed scripts do not execute.** If a script errors, no changes are made to the file. The file remains in the same state as before the call. This means there are no partial nodes, no orphaned elements from the failed script, and retrying after a fix is safe.
    
    ### When `use_figma` returns an error
    
    1. **STOP.** Do not immediately fix the code and retry.
    2. **Read the error message carefully.** Understand exactly what went wrong — wrong API usage, missing font, invalid property value, etc.
    3. **If the error is unclear**, call `get_metadata` or `get_screenshot` to understand the current file state.
    4. **Fix the script** based on the error message.
    5. **Retry** the corrected script.
    
    ### Common self-correction patterns
    
    | Error message | Likely cause | How to fix |
    |---|---|---|
    | `"not implemented"` | Used `figma.notify()` | Remove it — use `return` for output |
    | `"node must be an auto-layout frame..."` | Set `FILL`/`HUG` before appending to auto-layout parent | Move `appendChild` before `layoutSizingX = 'FILL'` |
    | `"Setting figma.currentPage is not supported"` | Used sync page setter | Use `await figma.setCurrentPageAsync(page)` |
    | Property value out of range | Color channel > 1 (used 0–255 instead of 0–1) | Divide by 255 |
    | `"Cannot read properties of null"` | Node doesn't exist (wrong ID, wrong page) | Check page context, verify ID |
    | Script hangs / no response | Infinite loop or unresolved promise | Check for `while(true)` or missing `await`; ensure code terminates |
    | `"The node with id X does not exist"` | Parent instance was implicitly detached by a child `detachInstance()`, changing IDs | Re-discover nodes by traversal from a stable (non-instance) parent frame |
    
    ### When the script succeeds but the result looks wrong
    
    1. Call `get_metadata` to check structural correctness (hierarchy, counts, positions).
    2. Call `get_screenshot` to check visual correctness. Look closely for cropped/clipped text (line heights cutting off content) and overlapping elements — these are common and easy to miss.
    3. Identify the discrepancy — is it structural (wrong hierarchy, missing nodes) or visual (wrong colors, broken layout, clipped content)?
    4. Write a targeted fix script that modifies only the broken parts — don't recreate everything.
    
    > For the full validation workflow, see [Validation & Error Recovery](references/validation-and-recovery.md).
    
    ## 7. Pre-Flight Checklist
    
    Before submitting ANY `use_figma` call, verify:
    
    - [ ] Code uses `return` to send data back (NOT `figma.closePlugin()`)
    - [ ] Code is NOT wrapped in an async IIFE (auto-wrapped for you)
    - [ ] `return` value includes structured data with actionable info (IDs, counts)
    - [ ] NO usage of `figma.notify()` anywhere
    - [ ] NO usage of `console.log()` as output (use `return` instead)
    - [ ] All colors use 0–1 range (not 0–255)
    - [ ] Fills/strokes are reassigned as new arrays (not mutated in place)
    - [ ] Page switches use `await figma.setCurrentPageAsync(page)` (sync setter throws)
    - [ ] `layoutSizingVertical/Horizontal = 'FILL'` is set AFTER `parent.appendChild(child)`
    - [ ] `loadFontAsync()` called BEFORE any text property changes
    - [ ] `lineHeight`/`letterSpacing` use `{unit, value}` format (not bare numbers)
    - [ ] `resize()` is called BEFORE setting sizing modes (resize resets them to FIXED)
    - [ ] For multi-step workflows: IDs from previous calls are passed as string literals (not variables)
    - [ ] New top-level nodes are positioned away from (0,0) to avoid overlapping existing content
    - [ ] ALL created/mutated node IDs are collected and included in the `return` value
    - [ ] Every async call (`loadFontAsync`, `setCurrentPageAsync`, `importComponentByKeyAsync`, etc.) is `await`ed — no fire-and-forget Promises
    
    ## 8. Discover Conventions Before Creating
    
    **Always inspect the Figma file before creating anything.** Different files use different naming conventions, variable structures, and component patterns. Your code should match what's already there, not impose new conventions.
    
    When in doubt about any convention (naming, scoping, structure), check the Figma file first, then the user's codebase. Only fall back to common patterns when neither exists.
    
    ### Quick inspection scripts
    
    **List all pages and top-level nodes:**
    ```js
    const pages = figma.root.children.map(p => `${p.name} id=${p.id} children=${p.children.length}`);
    return pages.join('\n');
    ```
    
    **List existing components across all pages:**
    ```js
    const results = [];
    for (const page of figma.root.children) {
      await figma.setCurrentPageAsync(page);
      page.findAll(n => {
        if (n.type === 'COMPONENT' || n.type === 'COMPONENT_SET')
          results.push(`[${page.name}] ${n.name} (${n.type}) id=${n.id}`);
        return false;
      });
    }
    return results.join('\n');
    ```
    
    **List existing variable collections and their conventions:**
    ```js
    const collections = await figma.variables.getLocalVariableCollectionsAsync();
    const results = collections.map(c => ({
      name: c.name, id: c.id,
      varCount: c.variableIds.length,
      modes: c.modes.map(m => m.name)
    }));
    return results;
    ```
    
    ## 9. Reference Docs
    
    Load these as needed based on what your task involves:
    
    | Doc | When to load | What it covers |
    |-----|-------------|----------------|
    | [gotchas.md](references/gotchas.md) | Before any `use_figma` | Every known pitfall with WRONG/CORRECT code examples |
    | [common-patterns.md](references/common-patterns.md) | Need working code examples | Script scaffolds: shapes, text, auto-layout, variables, components, multi-step workflows |
    | [plugin-api-patterns.md](references/plugin-api-patterns.md) | Creating/editing nodes | Fills, strokes, Auto Layout, effects, grouping, cloning, styles |
    | [api-reference.md](references/api-reference.md) | Need exact API surface | Node creation, variables API, core properties, what works and what doesn't |
    | [validation-and-recovery.md](references/validation-and-recovery.md) | Multi-step writes or error recovery | `get_metadata` vs `get_screenshot` workflow, mandatory error recovery steps |
    | [component-patterns.md](references/component-patterns.md) | Creating components/variants | combineAsVariants, component properties, INSTANCE_SWAP, variant layout, discovering existing components, metadata traversal |
    | [variable-patterns.md](references/variable-patterns.md) | Creating/binding variables | Collections, modes, scopes, aliasing, binding patterns, discovering existing variables |
    | [text-style-patterns.md](references/text-style-patterns.md) | Creating/applying text styles | Type ramps, font probing, listing styles, applying styles to nodes |
    | [effect-style-patterns.md](references/effect-style-patterns.md) | Creating/applying effect styles | Drop shadows, listing styles, applying styles to nodes |
    | [plugin-api-standalone.index.md](references/plugin-api-standalone.index.md) | Need to understand the full API surface | Index of all types, methods, and properties in the Plugin API |
    | [plugin-api-standalone.d.ts](references/plugin-api-standalone.d.ts) | Need exact type signatures | Full typings file — grep for specific symbols, don't load all at once |
    
    ## 10. Snippet examples
    
    You will see snippets throughout documentation here. These snippets contain useful plugin API code that can be repurposed. Use them as is, or as starter code as you go. If there are key concepts that are best documented as generic snippets, call them out and write to disk so you can reuse in the future.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related