Figma
Figma, Inc. v15.0.0
Is this plugin right for you?
Researched Oct 1, 2026Move between design creation, review and implementation. [1]
Useful for designers and frontend developers. Our assessment from the available sources.
What you can do
What you need
Pricing
Limited MCP reading is available on Starter; higher limits depend on both plan and seat. The displayed Professional Dev seat is $12/month; billing selection must be checked before purchase. [2] [3] [3]
Before you connect
Sources, unknowns & research method
We reviewed the saved listing and available official pages. Scenarios are our summaries of documented capabilities. This plugin has not been tested in a connected account. A missing price does not mean free access.
Still unknown
- A numeric price applicable to this integration has not been established.
- Publisher country has not been verified in this research pass.
- Saved marketplace listingchatgpt.com · Checked Oct 1, 2026 · Snapshot saved
- Official websitewww.figma.com · Checked Oct 1, 2026 · Snapshot saved
- Official websitedevelopers.figma.com · Checked Oct 1, 2026 · Snapshot saved
- Official websitedevelopers.figma.com · Checked Oct 1, 2026 · Snapshot saved
- Saved package manifestcodex-plugin-stats.com · Checked Sep 30, 2026 · Snapshot saved
Publisher description
Bring your Figma workflows for creating, reviewing, and implementing designs into your ChatGPT and Codex conversations. Send your product screens to Figma where they become editable layers using your designs system components. Share these designs and collaborate with your team, or send them back to Codex to implement the designs in your codebase. You can also create and edit polished decks in Figma Slides, or generate diagrams in FigJam.
Language: English · Automatically detected from descriptions.
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package author
- Figma, Inc.
Package observed Sep 30, 2026.
Files & skills
File archives
Skill instructions
figma-code-connect26 KB
---
name: figma-code-connect
description: Creates and maintains Figma Code Connect template files that map Figma components to code snippets. Use when the user mentions Code Connect, Figma component mapping, design-to-code translation, or asks to create/update .figma.ts or .figma.js files.
disable-model-invocation: false
---
# Code Connect
## Overview
Create Code Connect template files (`.figma.ts`) that map Figma components to code snippets. Given a Figma URL, follow the steps below to create a template.
> **You write `.figma.ts` template files ONLY — never `.figma.tsx`.** This skill produces *parserless templates*: a `.figma.ts` file whose default export uses a `` figma.code`...` `` tagged template. Do **NOT** write a `.figma.tsx` file and do **NOT** use `figma.connect()` — that is the separate **parser-based** Code Connect format (published a different way) and is the **wrong artifact** for this skill; output written as `.figma.tsx` is rejected outright. If a `.figma.tsx` already exists for a component, leave it untouched and add your `.figma.ts` template alongside it. A capable model may be tempted to reach for the more familiar `.figma.tsx` / `figma.connect()` pattern from memory — resist it; here the correct output is **always** `.figma.ts` + `figma.code`.
## Prerequisites
- **Figma MCP server must be connected** — verify that Figma MCP tools (e.g., `get_code_connect_suggestions`) are available before proceeding. If not, guide the user to enable the Figma MCP server and restart their MCP client.
- **Components must be published** — Code Connect only works with components published to a Figma team library. If a component is not published, inform the user and stop.
- **Organization or Enterprise plan required** — Code Connect is not available on Free or Professional plans.
- **URL must include `node-id`** — the Figma URL must contain the `node-id` query parameter.
- **TypeScript types** — for editor autocomplete and type checking in `.figma.ts` files `@figma/code-connect/figma-types` must be added to `types` in `tsconfig.json`:
```json
{
"compilerOptions": {
"types": ["@figma/code-connect/figma-types"]
}
}
```
## Step 1: Parse the Figma URL
Extract `fileKey` and `nodeId` from the URL:
| URL Format | fileKey | nodeId |
|---|---|---|
| `figma.com/design/:fileKey/:name?node-id=X-Y` | `:fileKey` | `X-Y` → `X:Y` |
| `figma.com/file/:fileKey/:name?node-id=X-Y` | `:fileKey` | `X-Y` → `X:Y` |
| `figma.com/design/:fileKey/branch/:branchKey/:name` | use `:branchKey` | from `node-id` param |
Always convert `nodeId` hyphens to colons: `1234-5678` → `1234:5678`.
**Worked example:**
Given: `https://www.figma.com/design/QiEF6w564ggoW8ftcLvdcu/MyDesignSystem?node-id=4185-3778`
- `fileKey` = `QiEF6w564ggoW8ftcLvdcu`
- `nodeId` = `4185-3778` → `4185:3778`
## Step 2: Discover Unmapped Components
The user may provide a URL pointing to a frame, instance, or variant — not necessarily a component set or standalone component. Call the MCP tool `get_code_connect_suggestions` with:
- `fileKey` — from Step 1
- `nodeId` — from Step 1 (colons format)
- `excludeMappingPrompt` — `true` (returns a lightweight list of unmapped components)
This tool identifies published components in the selection that don't yet have Code Connect mappings.
**Handle the response:**
- **"No published components found in this selection"** — the node contains no published components. Inform the user they need to publish the component to a team library in Figma first, then stop.
- **"All component instances in this selection are already connected to code via Code Connect"** — everything is already mapped. Inform the user and stop.
- **Normal response with component list** — extract the `mainComponentNodeId` for each returned component. Use these resolved node IDs (not the original from the URL) for all subsequent steps. If multiple components are returned (e.g. the user selected a frame containing several different component instances), repeat Steps 3–6 for each one.
## Step 3: Fetch Component Properties
Call the MCP tool `get_context_for_code_connect` with:
- `fileKey` — from Step 1
- `nodeId` — the resolved `mainComponentNodeId` from Step 2
- `clientFrameworks` — determine from `figma.config.json` `parser` field (e.g. `"react"` → `["react"]`)
- `clientLanguages` — infer from project file extensions (e.g. TypeScript project → `["typescript"]`, JavaScript → `["javascript"]`)
For multiple components, call the tool once per node ID.
The response contains the Figma component's **property definitions** — note each property's name and type:
- **TEXT** — text content (labels, titles, placeholders)
- **BOOLEAN** — toggles (show/hide icon, disabled state)
- **VARIANT** — enum options (size, variant, state)
- **INSTANCE_SWAP** — swappable nested instances tied to a specific component (icon, avatar)
- **SLOT** — flexible content regions (freeform layout, mixed children); use `getSlot()` in templates (not the same as INSTANCE_SWAP)
Save this property list — you will use it in Step 5 to write the template.
## Step 4: Identify the Code Component
If the user did not specify which code component to connect:
1. Check `figma.config.json` for `paths` and `importPaths` to find where components live
2. Search the codebase for a component matching the Figma component name. Check common directories (`src/components/`, `components/`, `lib/ui/`, `app/components/`) if `figma.config.json` doesn't specify paths
3. Read candidate files and compare their props interface against the Figma properties from Step 3 — look for matching variant types, size options, boolean flags, and slot props
4. If multiple candidates match, pick the one with the closest prop-interface match and explain your reasoning to the user
5. If no match is found, show the 2 closest candidates and ask the user to confirm or provide the correct path
**Confirm with the user** before proceeding to Step 5. Present the match: which code component you found, where it lives, and why it matches (prop correspondence, naming, purpose).
Read `figma.config.json` for import path aliases — the `importPaths` section maps glob patterns to import specifiers, and the `paths` section maps those specifiers to directories.
Read the code component's source to understand its props interface — this informs how to map Figma properties to code props in Step 5.
## Step 5: Create the Template File (.figma.ts)
### File location
Place the file alongside existing Code Connect files. Check `figma.config.json` `include` patterns for the correct directory. **Name it `ComponentName.figma.ts` — never `ComponentName.figma.tsx`.** The `.figma.tsx` extension is the parser-based format; do not create one or modify an existing one.
### Template structure
Every template file follows this structure:
```ts
// url=https://www.figma.com/file/{fileKey}/{fileName}?node-id={nodeId}
// source={path to code component from Step 4}
// component={code component name from Step 4}
import figma from 'figma'
const instance = figma.selectedInstance
// Extract properties from the Figma component (see property mapping below)
// ...
export default {
example: figma.code`<Component ... />`, // Required: code snippet
imports: ['import { Component } from "..."'], // Optional: import statements
id: 'component-name', // Required: unique identifier
metadata: { // Optional
nestable: true, // true = inline in parent, false = show as pill
props: {} // data accessible to parent templates
}
}
```
### Property mapping
Use the property list from Step 3 to extract values. For each Figma property type, use the corresponding method:
| Figma Property Type | Template Method | When to Use |
|---|---|---|
| TEXT | `instance.getString('Name')` | Labels, titles, placeholder text |
| BOOLEAN | `instance.getBoolean('Name', { true: ..., false: ... })` | Toggle visibility, conditional props |
| VARIANT | `instance.getEnum('Name', { 'FigmaVal': 'codeVal' })` | Size, variant, state enums |
| INSTANCE_SWAP | `instance.getInstanceSwap('Name')` | Swapped instance for a fixed component slot (then `hasCodeConnect()` / `executeTemplate()`) - do not confuse with the SLOT property below |
| SLOT | `instance.getSlot('Name')` | Freeform slot content only when the Figma property type is **SLOT**
| (child layer) | `instance.findInstance('LayerName')` | Named child instances without a property |
| (text layer) | `instance.findText('LayerName')` → `.textContent` | Text content from named layers |
**TEXT** — get the string value directly:
```ts
const label = instance.getString('Label')
```
**VARIANT** — map Figma enum values to code values:
```ts
const variant = instance.getEnum('Variant', {
'Primary': 'primary',
'Secondary': 'secondary',
})
const size = instance.getEnum('Size', {
'Small': 'sm',
'Medium': 'md',
'Large': 'lg',
})
```
**BOOLEAN** — simple boolean or mapped to values:
```ts
// Simple boolean
const disabled = instance.getBoolean('Disabled')
// Mapped to code values (e.g. when the code prop is an enum, not a boolean)
const size = instance.getBoolean('Show Label', { true: 'large', false: 'small' })
```
**Map Figma properties to code props where there's a valid correspondence.** Figma properties and code props don't always line up 1:1 — some Figma properties map directly (by name, or via the API methods above), others have no code equivalent. Where a mapping exists, use it; where none fits, omit the Figma property rather than invent a code prop. Never emit an attribute whose name doesn't appear in the code component's `Props` interface.
### Exhaustive variant handling
When a VARIANT property has multiple possible values, the `getEnum` mapping **must list every value** returned by `get_context_for_code_connect`. Don't omit values — an unmapped value silently returns `undefined`, producing broken output.
```ts
// WRONG — omits 'Warning', which will render as undefined
const status = instance.getEnum('Status', {
'Success': 'success',
'Error': 'error',
})
// CORRECT — every value is mapped
const status = instance.getEnum('Status', {
'Success': 'success',
'Error': 'error',
'Warning': 'warning',
'Info': 'info',
})
```
When **two or more VARIANT properties combine** to produce different code output, generate exhaustive conditional branches. For example, 2 variants × 2 values = 4 branches:
```ts
const type = instance.getEnum('Type', { 'Filled': 'filled', 'Outlined': 'outlined' })
const status = instance.getEnum('Status', { 'Success': 'success', 'Error': 'error' })
let colorClass
if (type === 'filled' && status === 'success') {
colorClass = 'bg-green-500 text-white'
} else if (type === 'filled' && status === 'error') {
colorClass = 'bg-red-500 text-white'
} else if (type === 'outlined' && status === 'success') {
colorClass = 'bg-transparent border-green-500'
} else if (type === 'outlined' && status === 'error') {
colorClass = 'bg-transparent border-red-500'
}
```
If the combinations produce **repetitive** output (e.g., `Size` doesn't change the snippet structure — it's just passed through as a prop), a single `getEnum` mapping per variant is sufficient — no need for cross-product branches.
**INSTANCE_SWAP** — access swappable component instances:
```ts
const icon = instance.getInstanceSwap('Icon')
let iconCode
if (icon && icon.type === 'INSTANCE') {
iconCode = icon.executeTemplate().example
}
```
**SLOT** — `getSlot(propName)` is only valid when the Figma component property reported in Step 3 has type **`SLOT`**. Do not use `getSlot()` for **INSTANCE_SWAP** properties (those use `getInstanceSwap()`). Slots are explicit “content regions” in the component definition, not generic nested instances.
- **Signature:** `getSlot(propName: string): ResultSection[] | undefined`
```ts
// Figma property "Content" must be type SLOT in component properties
const content = instance.getSlot('Content')
export default {
example: figma.code`<Card>${content}</Card>`,
// ...
}
```
### Interpolation in tagged templates
When interpolating values in tagged templates, use the correct wrapping:
- **String values** (`getString`, `getEnum`, `textContent`): wrap in quotes → `variant="${variant}"`
- **Instance/section values** (`executeTemplate().example`): wrap in braces → `icon={${iconCode}}`
- **Slot sections** (`getSlot()` result — `ResultSection[] | undefined`): interpolate directly inside `` figma.code`...` `` (same shape as nested snippet sections), e.g. `` figma.code`<Select>${content}</Select>` `` — do not treat as a plain string
- **Boolean bare props**: use conditional → `${disabled ? 'disabled' : ''}`
### Finding descendant layers
When you need to access children that aren't exposed as component properties:
| Method | Use when |
|---|---|
| `instance.getInstanceSwap('PropName')` | Figma property type is **INSTANCE_SWAP** (fixed swapped instance) |
| `instance.getSlot('PropName')` | Figma property type is **SLOT** (freeform content region) |
| `instance.findInstance('LayerName')` | You know the child layer name (no component property) |
| `instance.findText('LayerName')` → `.textContent` | You need text content from a named text layer |
| `instance.findConnectedInstance('id')` | You know the child's Code Connect `id` |
| `instance.findConnectedInstances(fn)` | You need multiple connected children matching a filter |
| `instance.findLayers(fn)` | You need any layers (text + instances) matching a filter |
### Nested configurable instances
A component may contain child instances that are **not exposed as component properties** (no INSTANCE_SWAP) but are still **independently configurable** — they have their own variants, properties, or swap slots. These must be resolved dynamically, not hardcoded.
1. **Check whether the child already has a Code Connect template** — use `get_code_connect_suggestions` or check existing `.figma.ts` files in the project.
2. **If no template exists, create one** for the child so it renders correctly both standalone and when nested.
3. **Reference the child from the parent** using `findInstance()` or `findConnectedInstance()`, then call `executeTemplate()`.
```ts
// Parent template — the Badge child isn't a prop, but it's configurable
const badge = instance.findInstance('Status Badge')
let badgeCode
if (badge && badge.type === 'INSTANCE') {
badgeCode = badge.executeTemplate().example
}
export default {
example: figma.code`<Card>${badgeCode}</Card>`,
// ...
}
```
This applies to icons, badges, labels, and any other nested instance that is configurable by itself — always connect them and render dynamically, never hardcode their content.
### Nested component example
For multi-level nested components or metadata prop passing between templates, see [advanced-patterns.md](references/advanced-patterns.md).
```ts
const icon = instance.getInstanceSwap('Icon')
let iconSnippet
if (icon && icon.type === 'INSTANCE') {
iconSnippet = icon.executeTemplate().example
}
export default {
example: figma.code`<Button ${iconSnippet ? figma.code`icon={${iconSnippet}}` : ''}>${label}</Button>`,
// ...
}
```
### Conditional props
```ts
const variant = instance.getEnum('Variant', { 'Primary': 'primary', 'Secondary': 'secondary' })
const disabled = instance.getBoolean('Disabled')
export default {
example: figma.code`
<Button
variant="${variant}"
${disabled ? 'disabled' : ''}
>
${label}
</Button>
`,
// ...
}
```
## Step 6: Validate
Read back the `.figma.ts` file and review it against the following:
- **Correct file type & format (check this FIRST)** — the file is `ComponentName.figma.ts` (NOT `.figma.tsx`), and its default export is a parserless template using a `` figma.code`...` `` tagged template. It must NOT use `figma.connect()` (the parser-based format). If you wrote `.figma.tsx` or `figma.connect()`, discard it and rewrite as a `.figma.ts` `figma.code` template.
- **Property coverage** — every Figma property from Step 3 should be accounted for in the template. Flag any that are missing and ask the user if they were intentionally omitted.
- **Valid, correctly typed code** — all emitted code must be valid and correctly typed against the code component's `Props` interface. Never make up component properties — if a Figma property has no corresponding code prop, omit it rather than invent one.
- **No hardcoded children** — verify that every INSTANCE_SWAP property and child component slot uses the dynamic APIs (`getInstanceSwap()`, `findInstance()`, `findConnectedInstance()`, etc.) with `executeTemplate()`. No slot should contain hardcoded component content.
- **Rules and Pitfalls** — check for the common mistakes listed below (string concatenation of template results, unnecessary `hasCodeConnect()` guards, missing `type === 'INSTANCE'` checks, etc.)
- **Interpolation wrapping** — strings (`getString`, `getEnum`, `textContent`) wrapped in quotes, instance/section values (`executeTemplate().example`) wrapped in braces, slot sections (`getSlot`) interpolated as snippet sections inside `` figma.code`...` ``, booleans using conditionals
If anything looks uncertain, consult [api.md](references/api.md) for API details and [advanced-patterns.md](references/advanced-patterns.md) for complex nesting.
## Inline Quick Reference
### `instance.*` Methods
| Method | Signature | Returns |
|---|---|---|
| `getString` | `(propName: string)` | `string` |
| `getBoolean` | `(propName: string, mapping?: { true: any, false: any })` | `boolean \| any` |
| `getEnum` | `(propName: string, mapping: { [figmaVal]: codeVal })` | `any` |
| `getInstanceSwap` | `(propName: string)` | `InstanceHandle \| null` |
| `getSlot` | `(propName: string)` | `ResultSection[] \| undefined` |
| `getPropertyValue` | `(propName: string)` | `string \| boolean` |
| `findInstance` | `(layerName: string, opts?: SelectorOptions)` | `InstanceHandle \| ErrorHandle` |
| `findText` | `(layerName: string, opts?: SelectorOptions)` | `TextHandle \| ErrorHandle` |
| `findConnectedInstance` | `(codeConnectId: string, opts?: SelectorOptions)` | `InstanceHandle \| ErrorHandle` |
| `findConnectedInstances` | `(selector: (node) => boolean, opts?: SelectorOptions)` | `InstanceHandle[]` |
| `findLayers` | `(selector: (node) => boolean, opts?: SelectorOptions)` | `(InstanceHandle \| TextHandle)[]` |
### InstanceHandle Methods
| Method | Returns |
|---|---|
| `hasCodeConnect()` | `boolean` |
| `executeTemplate()` | `{ example: ResultSection[], metadata: Metadata }` |
| `codeConnectId()` | `string \| null` |
### TextHandle Properties
| Property | Type |
|---|---|
| `.textContent` | `string` |
| `.name` | `string` |
### SelectorOptions
```ts
{ path?: string[], traverseInstances?: boolean }
```
- `traverseInstances: true` — required when the target lives inside another nested instance. Without it, `findInstance`/`findText` only search the current instance's own layers and stop at nested instance boundaries.
- `path: string[]` — disambiguates when multiple descendants share the same layer name. Lists parent layer names that must appear on the path to the target.
**Examples:**
```ts
// Layer hierarchy:
// A > C (instance) > "mychild"
// "mychild" sits inside nested instance C, so plain findInstance returns ErrorHandle.
instance.findInstance('mychild', { traverseInstances: true })
// Layer hierarchy:
// A > C (instance) > "mychild"
// A > D (instance) > "mychild"
// Two "mychild" layers exist — use path to pick the one under C.
instance.findInstance('mychild', { traverseInstances: true, path: ['C'] })
```
**When to reach into a nested instance from a parent template:** only when the parent code component (from Step 4) takes the nested layer as a prop value itself (e.g. `<C show={<B />} />` — A forwards B into C). If the parent just composes C and C renders B internally, resolve C with `executeTemplate()` and let C's own template handle B — don't duplicate B's rendering at the parent level.
### Export Structure
```ts
export default {
example: figma.code`...`, // Required: ResultSection[]
id: 'component-name', // Required: string
imports: ['import { X } from "..."'], // Optional: string[]
metadata: { nestable: true, props: {} } // Optional
}
```
## Rules and Pitfalls
1. **Never string-concatenate template results.** `executeTemplate().example` is a `ResultSection[]` object, not a string. Using `+` or `.join()` produces `[object Object]`. Always interpolate inside tagged templates: `` figma.code`${snippet1}${snippet2}` ``
2. **Do not use `hasCodeConnect()` guards.** Call `executeTemplate()` directly on any instance after a `type === 'INSTANCE'` check. The runtime handles instances without Code Connect automatically.
```ts
// WRONG — hasCodeConnect() gate drops non-CC instances
if (icon && icon.type === 'INSTANCE' && icon.hasCodeConnect()) {
iconCode = icon.executeTemplate().example
}
// CORRECT — let the runtime handle all instances
if (icon && icon.type === 'INSTANCE') {
iconCode = icon.executeTemplate().example
}
```
3. **Check `type === 'INSTANCE'` before calling `executeTemplate()`.** `findInstance()`, `findConnectedInstance()`, and `findText()` return an `ErrorHandle` (truthy, but not a real node) on failure — not `null`. Always add a type check to avoid crashes: `if (child && child.type === 'INSTANCE') { ... }`
4. **Prefer `getInstanceSwap()` over `findInstance()`** when a component property exists for the slot. `findInstance('Star Icon')` breaks when the icon is swapped to a different name; `getInstanceSwap('Icon')` always works regardless of which instance is in the slot.
5. **Use `getSlot()` only when the Figma property type is `SLOT`.** For **INSTANCE_SWAP** props, use `getInstanceSwap()` (returns an `InstanceHandle`). `getSlot()` returns structured slot sections, not instances — never call `executeTemplate()` on its return value.
6. **Property names are case-sensitive** and must exactly match what `get_context_for_code_connect` returns.
7. **Handle multiple template arrays correctly.** When iterating over children, set each result in a separate variable and interpolate them individually — do not use `.map().join()`:
```ts
// Wrong:
items.map(n => n.executeTemplate().example).join('\n')
// Correct — use separate variables:
const child1 = items[0]?.executeTemplate().example
const child2 = items[1]?.executeTemplate().example
export default { example: figma.code`${child1}${child2}` }
```
7. **Never hardcode slot or children content.** Always resolve child instances dynamically — use `getInstanceSwap()` for INSTANCE_SWAP properties, `findInstance()`/`findConnectedInstance()` for direct children — and render them via `executeTemplate()`. Never construct JSX from a layer name (e.g., `<StarIcon />`) or guess import paths. If an instance has no Code Connect, omit it — do not add a hardcoded fallback.
```ts
// WRONG — hardcodes the icon from its layer name
example: figma.code`<Button icon={<StarIcon />}>Submit</Button>`
// CORRECT — resolves dynamically, works for any swapped icon
const icon = instance.findInstance('Icon')
let iconCode
if (icon && icon.type === 'INSTANCE') {
iconCode = icon.executeTemplate().example
}
example: figma.code`<Button${iconCode ? figma.code` icon={${iconCode}}` : ''}>...</Button>`
```
8. **Attempt to represent every Figma property via a code prop.** The code component's `Props` interface (from Step 4) is the authoritative list of attribute names. For each Figma property, figure out the right way to represent it using the API methods from Step 5 — direct name match, value transformation, or whatever fits. If no code prop fits at all, omit it — don't invent a prop name.
## Complete Worked Example
Given URL: `https://figma.com/design/abc123/MyFile?node-id=42-100`
**Step 1:** Parse the URL.
- `fileKey` = `abc123`
- `nodeId` = `42-100` → `42:100`
**Step 2:** Call `get_code_connect_suggestions` with `fileKey: "abc123"`, `nodeId: "42:100"`, `excludeMappingPrompt: true`.
Response returns one component with `mainComponentNodeId: "42:100"`. If the response were empty, stop and inform the user. If multiple components were returned, repeat Steps 3–6 for each.
**Step 3:** Call `get_context_for_code_connect` with `fileKey: "abc123"`, `nodeId: "42:100"` (from Step 2), `clientFrameworks: ["react"]`, `clientLanguages: ["typescript"]`.
Response includes properties:
- Label (TEXT)
- Variant (VARIANT): Primary, Secondary
- Size (VARIANT): Small, Medium, Large
- Disabled (BOOLEAN)
- Has Icon (BOOLEAN)
- Icon (INSTANCE_SWAP)
**Step 4:** Search codebase → find `Button` component. Read its source to confirm props: `variant`, `size`, `disabled`, `icon`, `children`. Import path: `"primitives"`.
**Step 5:** Create `src/figma/primitives/Button.figma.ts`:
```ts
// url=https://figma.com/design/abc123/MyFile?node-id=42-100
// source=src/components/Button.tsx
// component=Button
import figma from 'figma'
const instance = figma.selectedInstance
const label = instance.getString('Label')
const variant = instance.getEnum('Variant', {
'Primary': 'primary',
'Secondary': 'secondary',
})
const size = instance.getEnum('Size', {
'Small': 'sm',
'Medium': 'md',
'Large': 'lg',
})
const disabled = instance.getBoolean('Disabled')
const hasIcon = instance.getBoolean('Has Icon')
const icon = hasIcon ? instance.getInstanceSwap('Icon') : null
let iconCode
if (icon && icon.type === 'INSTANCE') {
iconCode = icon.executeTemplate().example
}
export default {
example: figma.code`
<Button
variant="${variant}"
size="${size}"
${disabled ? 'disabled' : ''}
${iconCode ? figma.code`icon={${iconCode}}` : ''}
>
${label}
</Button>
`,
imports: ['import { Button } from "primitives"'],
id: 'button',
metadata: { nestable: true }
}
```
**Step 6:** Read back file to verify syntax.
## Additional Reference
For advanced patterns (multi-level nested components, `findConnectedInstances` filtering, metadata prop passing between parent/child templates):
- [api.md](references/api.md) — Full Code Connect API reference
- [advanced-patterns.md](references/advanced-patterns.md) — Advanced nesting, metadata props, and descendant patterns
Referenced files: 2
figma-create-new-file1.8 KB
---
name: figma-create-new-file
description: "Always use this skill when the user wants to create a new Figma Design, FigJam, or Slides file. You MUST invoke this skill BEFORE every `create_new_file` tool call."
disable-model-invocation: false
---
# Create a new file in Figma
Steps to create a new Figma Design, FigJam, or Slides file using the `create_new_file` MCP tool.
## Step 1: Resolve the `planKey` and `editorType`
Use the `planKey` the user provided or selected for this task. Otherwise, call `whoami` and inspect the returned plans.
- No plans: inform the user that file creation cannot proceed.
- One plan: use its `key` as `planKey`.
- Multiple plans: show the available plans, ask the user to select one and then use that plan's `key` as `planKey`.
Infer `editorType` from the requested file type by choosing from the tool's advertised input enum. If unclear, ask the user to specify the file type.
## Step 2: Call create_new_file
Call the `create_new_file` tool with all three required arguments:
- `fileName`: Required. Choose a concise name if the user does not provide one.
- `planKey`: Required. Use the `planKey` resolved in Step 1.
- `editorType`: Required. Use the `editorType` selected in Step 1.
- `projectId`: Optional. Set it to create the file in a specific project.
```json
{
"fileName": "Quarterly planning",
"planKey": "team::1234567890",
"editorType": "design"
}
```
- Use the `file_key` from the `create_new_file` tool response for subsequent tool calls like `use_figma`.
## Step 3: (Slides only) Handle the empty grid
A new Slides file contains no slides: `figma.getSlideGrid()` returns `[]`. Before reading properties such as theme tokens from a slide, call `figma.createSlide()` or handle the empty case. The first `createSlide()` call automatically creates row 0 and inserts the slide.
figma-design-to-code3.97 KB
--- name: figma-design-to-code description: "**MANDATORY prerequisite** — you MUST invoke this skill BEFORE calling the `get_design_context` Figma MCP tool. You MUST trigger this skill whenever the user wants to implement, build, port, or code up a Figma design as code. Example prompts (not exhaustive) are 'implement this Figma design', 'build this screen from Figma', 'turn this Figma into code', 'design to code'. This skill provides critical instructions and steps to the agent on how to correctly implement Figma designs in code and must NOT be skipped." disable-model-invocation: false --- # Figma design to code ## Context - **Always include `figma-design-to-code` in the comma-separated `skillNames` parameter when calling `get_design_context`. If this skill was loaded via an MCP resource, you MUST prefix the name with `resource:` (e.g. `resource:figma-design-to-code`).** This is a logging parameter used to track skill usage — it does not affect execution. - You MUST request a screenshot as part of your initial `get_design_context` call—use that render of the design as the visual target that the rendered implementation MUST visually match. - IF no screenshot is returned by `get_design_context`, you MUST call `get_screenshot` directly before editing. - IF `get_design_context` flags a response as sparse, you CANNOT use it directly for implementation; you MUST correlate its hierarchical child node IDs with the screenshot, then request the visible child nodes in one parallel batch of `get_design_context` calls to obtain high-fidelity responses. - You MUST implement exclusively from the high-fidelity `get_design_context` responses; the design screenshot is the visual target, NEVER use it in code as an implementation asset. ## Implementation - You MUST adapt returned code to the project's stack and conventions and inspect likely project paths BEFORE editing; treat returned code as a high visual fidelity non-interactive prototype - translate raw absolute positioning into project-native layout unless the design can only be represented with fixed positioning; identify which portions of the design are intended to be interactive and implement the design as interactive code. - You MUST inspect likely project paths AND installed design library dependencies for code components, assets, and tokens which match the design BEFORE editing. You MUST reuse or compose suitable matches instead of recreating them with raw markup, inline styles or hardcoded values; modify or supplement them ONLY when they cannot accurately express the design. - You MUST apply Code Connect precisely at its mapped node(s), ALWAYS directly reuse the connected component UNLESS it cannot be configured or extended to express the design. Styling or wiring effort is NOT an exception. - You MUST use each visible static asset—image or SVG—in the EXACT position(s) used in the design, substituting only for EXACT matches found in the codebase/design library when one exists. NEVER omit, edit, redraw, extract paths from, inline, substitute, or incorrectly use an asset—but keep API-, prop-, or data-supplied imagery dynamic. ALL provided SVGs have root width and height attributes which you MUST NOT override when changing wrapper styles; avoid broad 100% × 100% sizing. - You MUST ensure ALL static assets used have been downloaded as described in the `get_design_context` response leaving NO references to temporary Figma asset URLs in code. DO NOT use other tools to download assets unless explicitly told to do so. Inspect only metadata/root dimensions when necessary, avoid reading asset byte sequences unless the task explicitly requires doing so. ## Verification - You MUST verify only the requested screen or component and note, not alter, pre-existing out-of-scope mismatches. - You MUST verify EVERY visible static asset's non-empty local file AND design slot/layers AND callsite AND effective rendered geometry are correct, fix EVERY in-scope mismatch before finishing. A single substituted, mismatched, misproportioned asset is a FAIL.
figma-generate-design35.3 KB
---
name: figma-generate-design
description: "Use this skill alongside figma-use when the task involves translating an application page, view, or multi-section layout into Figma. Triggers: 'write to Figma', 'create in Figma from code', 'push page to Figma', 'take this app/page and build it in Figma', 'create a screen', 'build a landing page in Figma', 'update the Figma screen to match code', 'convert this modal/dialog/drawer/panel to Figma'. This is the preferred workflow skill whenever the user wants to build or update a full page, modal, dialog, drawer, sidebar, panel, or any composed multi-section view in Figma from code or a description. Discovers design system components, variables, and styles from Code Connect files, existing screens, and library search, then imports them and assembles views incrementally section-by-section using design system tokens instead of hardcoded values."
disable-model-invocation: false
---
# Build / Update Screens and Views from Design System
**Hard deliverable gate:** Code-to-design output must recreate the UI as editable text, components, icons, and hierarchy.
Treat any screenshot or flattened rendering of the complete UI only as a visual reference: never upload or import it into the deliverable, including as the wrapper fill or a child node, even if it exists in the repository or appears more pixel-accurate. Before completion, inspect the result; if the UI is not primarily represented by editable layers, rebuild it semantically or report failure.
Use this skill to create or update **screens, views, and multi-section UI containers** in Figma by **reusing the published design system** — components, variables, and styles — rather than drawing primitives with hardcoded values. This includes full pages, modals, dialogs, drawers, sidebars, panels, and any composed view with multiple sections. The key insight: the Figma file likely has a published design system with components, color/spacing variables, and text/effect styles that correspond to the codebase's UI components and tokens. Find and use those instead of drawing boxes with hex colors.
**MANDATORY**: You MUST also load [figma-use](../figma-use/SKILL.md) before any `use_figma` call. That skill contains critical rules (color ranges, font loading, etc.) that apply to every script you write.
**Always include `figma-generate-design` in the comma-separated `skillNames` parameter when calling `use_figma` as part of this skill. If this skill was loaded via an MCP resource, you MUST prefix the name with `resource:` (e.g. `resource:figma-generate-design`).** This is a logging parameter — it does not affect execution.
## Skill Boundaries
- Use this skill when the deliverable is a **composed Figma view** (new or updated) — full-page screens, modals, dialogs, drawers, sidebars, panels, or any multi-section container — built from design system component instances.
- If the user wants to create **new reusable components or variants**, use [figma-use](../figma-use/SKILL.md) directly.
- If the user wants to write **Code Connect mappings**, switch to [figma-code-connect](../figma-code-connect/SKILL.md).
## Prerequisites
- Figma MCP server must be connected
- The target Figma file must have a published design system with components (or access to a team library)
- User must provide a target Figma file (URL or `fileKey`). If they don't have one yet, invoke `/figma-create-new-file` (or call `create_new_file`) first and reuse the returned file_key. Both `use_figma` and `generate_figma_design` require an existing `fileKey`.
- Source code or description of the screen/view to build/update
## Parallel Workflow with generate_figma_design (Web Apps Only)
When building a screen from a **web app** that can be rendered in a browser, the best results come from running both approaches in parallel:
1. **In parallel:**
- Start building the screen using this skill's workflow (use_figma + design system components) against the target Figma file (`fileKey`).
- Run `generate_figma_design` against the **same `fileKey`** to capture a pixel-perfect screenshot of the running web app into that file. `generate_figma_design` always requires `fileKey` — if the user does not yet have a Figma file, first invoke `/figma-create-new-file` (or call the `create_new_file` MCP tool) to get one, and reuse that file_key for both this skill and the capture.
2. **Once both complete:** Update the use_figma output to match the pixel-perfect layout from the `generate_figma_design` capture. The capture provides the exact spacing, sizing, and visual treatment to aim for, while your use_figma output has proper component instances linked to the design system. If the capture contains images, transfer them to your use_figma output by copying `imageHash` values from the capture's image fills (see Step 5 for details).
3. **Once confirmed looking good:** Delete the `generate_figma_design` output — it was only used as a visual reference.
This combines the best of both: `generate_figma_design` gives pixel-perfect layout accuracy, while use_figma gives proper design system component instances that stay linked and updatable.
**This parallel workflow is MANDATORY when the source contains discrete content images used by the app, such as photos, avatars, logos, or icons. A screenshot or flattened rendering of the complete UI is a visual reference, not a content image.** The `use_figma` Plugin API cannot fetch external image URLs — it can only set image fills by copying `imageHash` values from nodes already in the file. `generate_figma_design` rasterizes all visible images into Figma, providing the hashes you need. If you skip the capture when these content images are present, their image frames will be left blank.
For non-web apps (iOS, Android, etc.) or when updating existing screens, use the standard workflow below.
## Required Workflow
**Follow these steps in order. Do not skip steps.**
> **Hard gates — forbidden shortcuts:**
>
> - **Forbidden:** `search_design_system` for component keys until 2a-i is complete and 2a-ii is attempted or logged N/A (e.g. "empty file, no existing screens").
> - **Forbidden:** Any `use_figma` call that mutates the canvas (Step 3+) until all Step 2 rows in the checklist below are filled in.
### Step 1: Understand the Deliverable
Before touching Figma, understand what you're building:
1. If building from code, read the relevant source files to understand the structure, sections, and which components are used.
2. Identify the major sections of the view (e.g., for a page: Header, Hero, Content Panels, Footer; for a modal: Title Bar, Form Sections, Action Bar; for a sidebar: Navigation, Content Area, Footer Actions).
3. For each section, list the UI components involved (buttons, inputs, cards, navigation pills, accordions, etc.).
4. **Identify the product's font family from the source. Do not default to Inter.** Find *which* typeface the product uses before writing any script. See [references/discover-product-font.md](references/discover-product-font.md) for where to look (CSS variables, component files) and how to resolve messy Figma font names.
5. **Check whether the view contains any images** (e.g., `<img>`, `<Image>`, background images, product photos, avatars, icons loaded from URLs). If it does and this is a web app, you **must** run the parallel `generate_figma_design` capture workflow — start it immediately alongside Step 2 so the capture runs while you discover components. See "Parallel Workflow with generate_figma_design" above.
### Step 2: Collect Component Keys, Variables, and Styles
You need three things from the design system: **components** (buttons, cards, etc.), **variables** (colors, spacing, radii), and **styles** (text styles, effect styles like shadows). Don't hardcode hex colors or pixel values when design system tokens exist.
#### 2a: Discover components
**2a-i — REQUIRED: Check Code Connect for needed components.** Starting from the component list you built in Step 1, check whether each component has a Code Connect file in the codebase. Code Connect files live next to the component source and are named by platform:
- **TypeScript/JS**: `*.figma.ts`, `*.figma.js`
- **React (parser-based)**: `*.figma.tsx`
- **Kotlin/Compose**: `.kt` files containing `@FigmaConnect`
- **Swift**: `.swift` files containing `FigmaConnect`
For each component you need (e.g., Button, Card, Input), search for its Code Connect file — glob or grep by component name (e.g., `**/Button.figma.tsx`, `**/Card.figma.ts`). Only read files that match components you actually need.
From each matching Code Connect file, extract the Figma component URL. Parse `fileKey` and `nodeId` from the URL (convert hyphens to colons: `123-456` → `123:456`). Then resolve component keys via `use_figma`:
**Example:** Code Connect file contains `// url=https://figma.com/design/ABC123/File?node-id=609-35535`. Parse `fileKey` = `ABC123`, `nodeId` = `609:35535`. Run `use_figma` against the **library file** (fileKey `ABC123`, not the target file) to resolve the key:
```js
const node = await figma.getNodeByIdAsync("609:35535");
const set = node?.parent?.type === "COMPONENT_SET" ? node.parent : node;
return { componentKey: set.key };
```
Batch multiple lookups in a single call. Use the returned keys with `importComponentSetByKeyAsync()` in Step 4.
Mark resolved components. If all components are resolved, skip 2a-ii and 2a-iii. If none of the needed components have Code Connect files, proceed to 2a-ii.
**2a-ii — REQUIRED if unresolved components remain: Inspect existing screens.** Check if the target file already contains screens using the same design system. A single `use_figma` call that walks an existing frame's instances gives you an exact, authoritative component map:
```js
const frame = figma.currentPage.findOne(n => n.name === "Existing Screen");
const uniqueSets = new Map();
frame.findAllWithCriteria({ types: ["INSTANCE"] }).forEach(inst => {
const mc = inst.mainComponent;
const cs = mc?.parent?.type === "COMPONENT_SET" ? mc.parent : null;
const key = cs ? cs.key : mc?.key;
const name = cs ? cs.name : mc?.name;
if (key && !uniqueSets.has(key)) {
uniqueSets.set(key, { name, key, isSet: !!cs, sampleVariant: mc.name });
}
});
return [...uniqueSets.values()];
```
Match results against your unresolved components. Mark any newly resolved. If all components are resolved, skip 2a-iii.
**2a-iii — LAST RESORT: `search_design_system`.** Only if components remain unresolved after completing both 2a-i and 2a-ii.
Before searching, call `get_libraries` to discover which libraries are available for the file. This returns two lists: libraries already added to the file and libraries available to add (community UI kits and org libraries). Each entry includes a `libraryKey` you can pass to `search_design_system` via the `includeLibraryKeys` param to scope your search to specific libraries instead of searching across everything.
```
// Step 1: Discover available libraries
get_libraries({ fileKey })
// Returns: {
// libraries_added_to_file: [...],
// libraries_available_to_add: [...],
// libraries_available_to_add_next_offset: number | null
// }
// Step 2: Search within a specific library using its libraryKey
search_design_system({ queries: [{ entity: "component", query: "button" }], fileKey, includeLibraryKeys: ["lk-abc123..."] })
```
Org libraries in `libraries_available_to_add` are paginated (20 per page). When `libraries_available_to_add_next_offset` is non-null, more org libraries are available — call `get_libraries` again with `offset` set to that value to fetch the next page. Community UI kits only appear on the first page. If the user names a specific library you don't see in the current page, page further before giving up.
This is especially useful when the file has many libraries and you want targeted results (e.g. searching only within "iOS 26" or "Material 3" instead of getting matches from every library).
**Search broadly, but one intent per query** — `search_design_system` does NOT apply OR semantics, so never pack alternatives or synonyms into a single string ("Button IconButton icon" matches nothing useful). Pass each term as a component entry in one `queries` call: `{ entity: "component", query: "button" }`, `{ entity: "component", query: "input" }`, `{ entity: "component", query: "nav" }`, etc. Multi-word names and phrases are fine when they name one thing ("Material Design Icons").
**Include component properties** in your map — you need to know which TEXT properties each component exposes for text overrides. Create a temporary instance, read its `componentProperties` (and those of nested instances), then remove the temp instance.
Example component map with property info:
```
Component Map:
- Button → key: "abc123", type: COMPONENT_SET
Properties: { "Label#2:0": TEXT, "Has Icon#4:64": BOOLEAN }
- PricingCard → key: "ghi789", type: COMPONENT_SET
Properties: { "Device": VARIANT, "Variant": VARIANT }
Nested "Text Heading" has: { "Text#2104:5": TEXT }
Nested "Button" has: { "Label#2:0": TEXT }
```
#### 2b: Discover variables (colors, spacing, radii)
**Inspect existing screens first** (same as components). Or use `search_design_system` with `queries` entries whose `entity` is `"variable"`.
> **WARNING: Two different variable discovery methods — do not confuse them.**
>
> - `use_figma` with `figma.variables.getLocalVariableCollectionsAsync()` — returns **only local variables defined in the current file**. If this returns empty, it does **not** mean no variables exist. Remote/published library variables are invisible to this API.
> - `search_design_system` with `entity: "variable"` query entries — searches across **all linked libraries**, including remote and published ones. This is the correct tool for discovering design system variables.
>
> **Never conclude "no variables exist" based solely on `getLocalVariableCollectionsAsync()` returning empty.** Always also run `search_design_system` with variable query entries to check for library variables before deciding to create your own.
**Query strategy:** `search_design_system` matches against **variable names** (e.g., "Gray/gray-9", "core/gray/100", "space/400"), not categories. Put multiple short, simple searches in one `queries` call rather than one compound query. Every array entry must be an `{ "entity": "variable", "query": "..." }` object, never a bare string:
- **Primitive colors:** `{ "entity": "variable", "query": "gray" }`, `{ "entity": "variable", "query": "red" }`, etc.
- **Semantic colors:** `{ "entity": "variable", "query": "background" }`, `{ "entity": "variable", "query": "surface" }`, etc.
- **Spacing/sizing:** `{ "entity": "variable", "query": "space" }`, `{ "entity": "variable", "query": "radius" }`, etc.
If initial searches return empty, try shorter fragments or different naming conventions — libraries vary widely ("grey" vs "gray", "spacing" vs "space", "color/bg" vs "background").
Inspect an existing screen's bound variables for the most authoritative results:
```js
const frame = figma.currentPage.findOne(n => n.name === "Existing Screen");
// boundVariables can live on any scene node — enumerating every scene type
// just to feed findAllWithCriteria is roughly the same as findAll(() => true)
// and is much noisier in script output.
const uniqueIds = new Set(
frame.findAll(() => true).flatMap(n =>
Object.values(n.boundVariables ?? {})
.flatMap(b => Array.isArray(b) ? b : [b])
.map(b => b?.id)
.filter(Boolean)
)
);
const variables = await Promise.all(
[...uniqueIds].map(id => figma.variables.getVariableByIdAsync(id))
);
return variables
.filter(Boolean)
.map(v => ({ name: v.name, id: v.id, key: v.key, type: v.resolvedType, remote: v.remote }));
```
For library variables (remote = true), import them by key with `figma.variables.importVariableByKeyAsync(key)`. For local variables, use `figma.variables.getVariableByIdAsync(id)` directly.
See [variable-patterns.md](../figma-use/references/variable-patterns.md) for binding patterns.
#### 2c: Discover styles (text styles, effect styles)
Search for styles using `search_design_system` with `entity: "style"` query entries and terms like "heading", "body", "shadow", "elevation". Or inspect what an existing screen uses:
```js
const frame = figma.currentPage.findOne(n => n.name === "Existing Screen");
const styles = { text: new Map(), effect: new Map() };
for (const node of frame.findAll(() => true)) {
// textStyleId is on TEXT and TEXT_PATH; effectStyleId is on most scene
// shape/container types. Use `in` guards to handle both without an
// exhaustive type list.
if ('textStyleId' in node && node.textStyleId) {
const s = figma.getStyleById(node.textStyleId);
if (s) styles.text.set(s.id, { name: s.name, id: s.id, key: s.key });
}
if ('effectStyleId' in node && node.effectStyleId) {
const s = figma.getStyleById(node.effectStyleId);
if (s) styles.effect.set(s.id, { name: s.name, id: s.id, key: s.key });
}
}
return {
textStyles: [...styles.text.values()],
effectStyles: [...styles.effect.values()]
};
```
Import library styles with `figma.importStyleByKeyAsync(key)`, then apply with `node.textStyleId = style.id` or `node.effectStyleId = style.id`.
See [text-style-patterns.md](../figma-use/references/text-style-patterns.md) and [effect-style-patterns.md](../figma-use/references/effect-style-patterns.md) for details.
### Step 3: Create the Wrapper Frame First
**Do NOT build sections as top-level page children and reparent them later** — moving nodes across `use_figma` calls with `appendChild()` silently fails and produces orphaned frames. Instead, create the wrapper first, then build each section directly inside it.
Create the wrapper in its own `use_figma` call. Position it away from existing content and return its ID:
```js
// Find clear space
let maxX = 0;
for (const child of figma.currentPage.children) {
maxX = Math.max(maxX, child.x + child.width);
}
const wrapper = figma.createAutoLayout("VERTICAL");
// --- Size the wrapper based on container type ---
// Full page: wrapper.resize(1440, 100); wrapper.name = "Homepage";
// Modal/dialog: wrapper.resize(640, 100); wrapper.name = "Settings Modal";
// Drawer/sidebar: wrapper.resize(360, 100); wrapper.name = "Navigation Drawer";
// Panel: wrapper.resize(400, 100); wrapper.name = "Details Panel";
// Adapt width to match the source code's actual dimensions.
wrapper.name = "VIEW_NAME";
wrapper.primaryAxisAlignItems = "CENTER";
wrapper.counterAxisAlignItems = "CENTER";
wrapper.resize(WIDTH, 100);
wrapper.layoutSizingHorizontal = "FIXED";
wrapper.x = maxX + 200;
wrapper.y = 0;
return { success: true, wrapperId: wrapper.id };
```
### Step 4: Build the Sections Inside the Wrapper
**This is the most important step.** Build in retry-safe construction phases: related sections may share one `use_figma` call when the resulting script is safe to retry. Split a phase only when it would cross page context, when partial execution would be hard to recover, or when an actual failure requires a targeted retry — not merely to create a validation checkpoint. At the start of each script, fetch the wrapper by ID and append new content directly to it.
```js
const createdNodeIds = [];
// Resolve the wrapper and import every design system dependency in parallel.
// Sequential awaits here serialize N independent IPC round-trips at the top
// of every section build; one Promise.all is dramatically faster.
const [wrapper, buttonSet, bgColorVar, spacingVar, shadowStyle] = await Promise.all([
figma.getNodeByIdAsync("WRAPPER_ID_FROM_STEP_3"),
figma.importComponentSetByKeyAsync("BUTTON_SET_KEY"),
figma.variables.importVariableByKeyAsync("BG_COLOR_VAR_KEY"),
figma.variables.importVariableByKeyAsync("SPACING_VAR_KEY"),
figma.importStyleByKeyAsync("SHADOW_STYLE_KEY"),
]);
const primaryButton = buttonSet.children.find(c =>
c.type === "COMPONENT" && c.name.includes("variant=primary")
) || buttonSet.defaultVariant;
// Build section frame with variable bindings (not hardcoded values)
const section = figma.createAutoLayout();
section.name = "Header";
section.setBoundVariable("paddingLeft", spacingVar);
section.setBoundVariable("paddingRight", spacingVar);
const bgPaint = figma.variables.setBoundVariableForPaint(
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', bgColorVar
);
section.fills = [bgPaint];
// Apply the effect style imported above
section.effectStyleId = shadowStyle.id;
// Create component instances inside the section
const btnInstance = primaryButton.createInstance();
section.appendChild(btnInstance);
createdNodeIds.push(btnInstance.id);
// Append section to wrapper
wrapper.appendChild(section);
section.layoutSizingHorizontal = "FILL"; // AFTER appending
createdNodeIds.push(section.id);
return { success: true, createdNodeIds };
```
Return the created node IDs plus the relevant counts, names, and bounds from each call — this is your default structural validation. Run a separate structural read only when that evidence is missing or when a relevant mutation has invalidated it. Save the visual pass for Step 5.
#### Override instance text with setProperties()
Component instances ship with placeholder text ("Title", "Heading", "Button"). Use the component property keys you discovered in Step 2 to override them with `setProperties()` — this is more reliable than direct `node.characters` manipulation. See [component-patterns.md](../figma-use/references/component-patterns.md#overriding-text-in-a-component-instance) for the full pattern.
For nested instances that expose their own TEXT properties, call `setProperties()` on the nested instance:
```js
// Use the type-indexed criteria for the type filter, then narrow by name.
const nestedHeading = cardInstance
.findAllWithCriteria({ types: ["INSTANCE"] })
.find(n => n.name === "Text Heading");
if (nestedHeading) {
nestedHeading.setProperties({ "Text#2104:5": "Actual heading from source code" });
}
```
Only fall back to direct `node.characters` for text that is NOT managed by any component property.
#### Read source code defaults carefully
When translating code components to Figma instances, check the component's default prop values in the source code, not just what's explicitly passed. For example, `<Button size="small">Register</Button>` with no variant prop — check the component definition to find `variant = "primary"` as the default. Selecting the wrong variant (e.g., Neutral instead of Primary) produces a visually incorrect result that's easy to miss.
#### What to build manually vs. import from design system
| Build manually | Import from design system |
|----------------|--------------------------|
| Wrapper frame | **Components**: buttons, cards, inputs, nav, etc. |
| Section container frames | **Variables**: colors (fills, strokes), spacing (padding, gap), radii |
| Layout grids (rows, columns) | **Text styles**: heading, body, caption, etc. |
| | **Effect styles**: shadows, blurs, etc. |
**Never hardcode hex colors or pixel spacing** when a design system variable exists. Use `setBoundVariable` for spacing/radii and `setBoundVariableForPaint` for colors. Apply text styles with `node.textStyleId` and effect styles with `node.effectStyleId`.
#### Componentize repeated and reusable elements (required)
Componentization is part of the **default** workflow, not an optional follow-up. Produce a componentized structure on the first pass; do not emit a flat tree of one-off frames and wait for a second "now make it componentized" prompt.
- **Design-system instances are already componentized** (Step 2). Prefer them.
- **For anything the design system does not cover that repeats or maps to a reusable source component, create a local component once with `figma.createComponent()` and place instances**, instead of hand-building N near-identical frames. One source component maps to one Figma main component.
See [references/componentization.md](references/componentization.md) for the build-once-place-instances pattern and code.
#### Icons: import the SVG, never reconstruct from rotated primitives
Icons are the **main exception to the build-manually-vs-import split above.** If the design system exposes an icon as a component, instance it (a single INSTANCE_SWAP property, not a variant per icon). Otherwise — most commonly when **grabbing an icon from the codebase to place or replace it in Figma** — import the icon's **SVG source directly** as a vector node. This is the primary, default path for icons; do not redraw them.
1. **Get the SVG from the codebase.** Read the icon's source — inline `<svg>`, the imported `.svg` asset, or the icon-library entry — and pass that exact SVG string. Prefer the codebase's own SVG over hand-authoring one.
2. **Import with `figma.createNodeFromSvg(svgString)`**, which returns a `FrameNode` of editable vector paths. The SVG string **must** include a `viewBox` plus explicit `width`/`height` (e.g. `<svg width="24" height="24" viewBox="0 0 24 24" ...>`). Without `width`/`height` it falls back to the `viewBox` size, which is often smaller than the slot — the usual cause of "the icon didn't size properly."
3. **Size it to the slot.** `createNodeFromSvg` frames scale their contents on resize, so `icon.resize(size, size)` fits the whole icon (stroke weight included) to the target box. Equivalently author `width`/`height` equal to the target. Match the source's icon size — commonly 16/20/24px.
4. **Never reconstruct an icon from rotated line/rect/ellipse primitives.** Figma's line rotation is unreliable in the `use_figma` context and produces broken, mis-rotated icons (a chevron collapses into a blob, an arrowhead detaches from its shaft). Importing the SVG is both more reliable and more editable.
```js
// Place / replace an icon from a codebase SVG into a 24px slot
const icon = figma.createNodeFromSvg(
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">' +
'<path d="m9 18 6-6-6-6" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
);
icon.name = "icon/chevron-right";
icon.resize(24, 24); // scales the whole icon to the slot
slotFrame.appendChild(icon);
```
**Codebase SVGs usually use `currentColor`** (e.g. `stroke="currentColor"` / `fill="currentColor"`), which `createNodeFromSvg` imports as **black** — it does not inherit the parent's color. Set the intended color after import: substitute the literal color into the SVG string before importing, or bind the imported vector fills/strokes to a design-system color variable with `setBoundVariableForPaint` (same as any paint). To turn an imported SVG into a reusable icon component (for INSTANCE_SWAP), see [figma-generate-library → Creating Icon Components](../figma-generate-library/references/component-creation.md) and the [INSTANCE_SWAP pattern](../figma-use/references/component-patterns.md#instance_swap-avoiding-variant-explosion).
### Step 5: Validate the Full View and Transfer Images
Before visual validation, use `use_figma` to read back the wrapper and return its total descendant count, counts for every descendant node type present, and the ID, name, node type, and dimensions of every image-filled node. These counts are evidence, not a numeric pass threshold; do not infer editability from a screenshot or accept token editable nodes layered over a complete-UI raster. If a screenshot or flattened rendering of the complete UI is present anywhere inside the wrapper, remove it and rebuild the UI semantically or report failure; do not claim completion.
After composing all sections, take **one full-view composition screenshot** of the wrapper frame and compare against the source. If it reveals a meaningful visual defect, apply targeted `use_figma` fixes — don't rebuild the entire view — then take **one** post-fix screenshot. The most recent passing screenshot is the final check: do not take an additional unchanged "final" shot, and do not screenshot every section individually.
Inspect the composition screenshot for:
- **Cropped/clipped text** — line heights or frame sizing cutting off descenders, ascenders, or entire lines
- **Overlapping content** — elements stacking on top of each other due to incorrect sizing or missing auto-layout
- Placeholder text still showing ("Title", "Heading", "Button")
- Truncated content from layout sizing bugs
- Wrong component variants (e.g., Neutral vs Primary button)
- **Wrong font family** — text rendered in a different typeface than the product uses (e.g. Inter where the product is SF Pro). The script ran without error, so this is invisible at a glance; assert it explicitly (see "Assert the font family is correct" below)
- **Blank image placeholders** — if images are missing, you need to transfer them from the `generate_figma_design` capture (see below)
#### Assert the font family is correct
**You MUST explicitly assert that rendered text uses the product font(s) identified in Step 1**, and treat any mismatch as a failed validation. Do not assume a successfully loaded font is the correct font: loading Inter when the product uses SF Pro is a failure even if no errors occur. **If you have a source reference (the running web app, a design mock, or the `generate_figma_design` capture), you MUST also compare rendered screenshots** — a near-miss style within the right family can pass a family check but still look wrong.
See [references/discover-product-font.md](references/discover-product-font.md#verify-the-font-after-building) for the read-back script (which separates free-standing text you fix from design-system-governed text you flag) and the screenshot-comparison steps.
#### Transfer images from the generate_figma_design capture
If you ran `generate_figma_design` in parallel, transfer only discrete content assets used by the app into your design system output. Never upload or transfer the capture root or any screenshot or flattened rendering of the complete UI.
1. Find all image nodes in the capture output by searching for fills with `type === "IMAGE"`:
```js
const capture = await figma.getNodeByIdAsync("CAPTURE_NODE_ID");
const imageNodes = capture.findAll(() => true).flatMap(n => {
if (!Array.isArray(n.fills)) return [];
const imageFill = n.fills.find(f => f.type === "IMAGE");
return imageFill ? [{ name: n.name, id: n.id, imageHash: imageFill.imageHash }] : [];
});
return imageNodes;
```
2. Match each captured image to the corresponding frame in your use_figma output (by position, name, or order).
3. Apply the image hash to the target frame:
```js
targetFrame.fills = [{ type: "IMAGE", imageHash: "hash_from_capture", scaleMode: "FILL" }];
```
4. Delete the `generate_figma_design` capture output after all images are transferred.
### Step 6: Updating an Existing View
When updating rather than creating from scratch:
1. Use `get_metadata` to inspect the existing screen structure.
2. Identify which sections need updating and which can stay.
3. For each section that needs changes:
- Locate the existing nodes by ID or name
- Swap component instances if the design system component changed
- Update text content, variant properties, or layout as needed
- Remove deprecated sections
- Add new sections
4. Follow the same evidence and screenshot cadence as Steps 4–5: rely on returned IDs plus relevant counts, names, and bounds for structural validation, then take one full-view screenshot after the updates (and one post-fix screenshot only if a targeted visual fix is needed).
```js
// Example: Swap a button variant in an existing screen.
// Batch the node lookup and component-set import in parallel — they are
// independent and awaiting them sequentially serializes two IPC round-trips.
const [existingButton, buttonSet] = await Promise.all([
figma.getNodeByIdAsync("EXISTING_BUTTON_INSTANCE_ID"),
figma.importComponentSetByKeyAsync("BUTTON_SET_KEY"),
]);
if (existingButton && existingButton.type === "INSTANCE") {
const newVariant = buttonSet.children.find(c =>
c.name.includes("variant=primary") && c.name.includes("size=lg")
) || buttonSet.defaultVariant;
existingButton.swapComponent(newVariant);
}
return { success: true, mutatedNodeIds: [existingButton.id] };
```
## Reference Docs
For detailed API patterns and gotchas, load these from the [figma-use](../figma-use/SKILL.md) references as needed:
- [component-patterns.md](../figma-use/references/component-patterns.md) — importing by key, finding variants, setProperties, text overrides, working with instances
- [variable-patterns.md](../figma-use/references/variable-patterns.md) — creating/binding variables, importing library variables, scopes, aliasing, discovering existing variables
- [text-style-patterns.md](../figma-use/references/text-style-patterns.md) — creating/applying text styles, importing library text styles, type ramps
- [effect-style-patterns.md](../figma-use/references/effect-style-patterns.md) — creating/applying effect styles (shadows), importing library effect styles
- [gotchas.md](../figma-use/references/gotchas.md) — layout pitfalls (HUG/FILL interactions, counterAxisAlignItems, sizing order), paint/color issues, page context resets
## Error Recovery
Follow [figma-use error recovery](../figma-use/SKILL.md#7-error-recovery--self-correction):
- If `safeToRetryWithoutCanvasRead` is `true`, fix the error and retry.
- If `false`, read the canvas, determine what changed, then make changes.
Because this skill works in retry-safe construction phases, errors are naturally scoped to the current phase. Content from previous successful calls remains intact.
## Best Practices
- **Always search before building.** The design system likely has the component, variable, or style you need. Manual construction and hardcoded values should be the exception, not the rule.
- **Search broadly, one intent per query.** Try synonyms and partial terms as separate `{ entity, query }` entries in one `queries` call, never combined into one string — a "NavigationPill" might be found under "pill", "nav", "tab", or "chip", so pass those as four component entries. For variables, use `entity: "variable"` with queries like "color", "spacing", "radius", etc.
- **Prefer design system tokens over hardcoded values.** Use variable bindings for colors, spacing, and radii. Use text styles for typography. Use effect styles for shadows. This keeps the screen linked to the design system.
- **Prefer component instances over manual builds.** Instances stay linked to the source component and update automatically when the design system evolves.
- **Componentize by default.** Build repeated or reusable elements as a component once, then place instances. Do not ship a flat tree of one-off frames that needs a second "make it componentized" pass.
- **Work in retry-safe construction phases.** Batch related sections into one `use_figma` call when the script stays safe to retry; split only at page-context boundaries, hard-to-recover mutations, or a targeted retry after an actual failure.
- **Return node IDs and validation evidence from every call.** Return created IDs plus the relevant counts, names, and bounds — this is your default structural validation, and you'll need the IDs to compose sections and for error recovery.
- **Validate visually with one composition screenshot.** Take one full-view screenshot after composition, and one more only after a targeted visual fix; the most recent passing screenshot is final.
- **Assert the font family, not just a successful load.** A script can load the wrong font without error. After building, verify rendered text uses the product font identified in Step 1 (see Step 5).
- **Match existing conventions.** If the file already has screens, match their naming, sizing, and layout patterns.
Referenced files: 2
figma-generate-diagram10 KB
---
name: figma-generate-diagram
description: "MANDATORY prerequisite — load this skill BEFORE every `generate_diagram` tool call. NEVER call `generate_diagram` directly without loading this skill first. Trigger whenever the user asks to create, generate, draw, render, sketch, or build a diagram — flowchart, architecture diagram, sequence diagram, ERD or entity-relationship diagram, state diagram or state machine, gantt chart, or timeline. Also trigger when the user mentions Mermaid syntax or wants a system architecture, decision tree, dependency graph, API call flow, auth handshake, schema, or pipeline visualized in FigJam. Routes to type-specific guidance, sets universal Mermaid constraints, and tells you when to use a different diagram type or skip the tool entirely (mindmaps, pie charts, class diagrams, etc.)."
---
# generate-diagram
**You MUST load this skill before every `generate_diagram` tool call.** Skipping it causes preventable rendering failures and low-quality output.
`generate_diagram` takes Mermaid.js syntax and produces an editable FigJam diagram. This skill routes you to the right per-type guidance and sets universal constraints.
## Step 1: Is `generate_diagram` the right tool?
### Supported diagram types
`flowchart`, `sequenceDiagram`, `stateDiagram` / `stateDiagram-v2`, `gantt`, `erDiagram`.
### Unsupported — don't call the tool
If the user wants any of these, tell them directly that `generate_diagram` doesn't support it instead of calling the tool and failing:
- **Pie chart, mindmap, venn diagram, class diagram, journey, timeline, quadrant, C4, git graph, requirement diagram**
### When to push the user to edit in Figma
The tool cannot:
- Change fonts on an existing diagram
- Move individual shapes
- Edit a diagram node-by-node after generation
If the user asks for any of those on an existing diagram, recommend they open the diagram in Figma and edit there. For content-level changes, it's usually faster to regenerate.
## Step 2: Pick the diagram type
Lightweight routing — use the first match.
| User wants… | Type | Next step |
|---|---|---|
| Services + datastores + queues + integrations | **Architecture flowchart** | Read [references/architecture.md](./references/architecture.md) |
| Decision tree, process flow, pipeline, dependency graph, user journey | **Flowchart** | Read [references/flowchart.md](./references/flowchart.md) |
| Interactions between parties over time (API calls, auth, messaging) | **Sequence diagram** | Read [references/sequence.md](./references/sequence.md) |
| Data model, tables, keys, cardinality | **ER diagram** | Read [references/erd.md](./references/erd.md) |
| Named states with transitions between them | **State diagram** | Read [references/state.md](./references/state.md) |
| Project schedule with dates, milestones | **Gantt chart** | Read [references/gantt.md](./references/gantt.md) |
If a flowchart is requested and it describes software infrastructure (services, datastores, queues, external integrations), route to `architecture.md` — not `flowchart.md`. When in doubt, ask the user.
## Step 3: Universal constraints (apply to every diagram type)
1. **No emojis** in any part of the Mermaid source. The tool rejects them.
2. **No `\n`** in labels. Use newlines only when absolutely required and only via actual line breaks (not the escape sequence).
3. **No HTML tags** in labels.
4. **Reserved words** — don't use `end`, `subgraph`, `graph` as node IDs.
5. **Node IDs**: camelCase (`userService`), no spaces. Underscores can break edge routing in some processors.
6. **Special characters in labels** must be wrapped in quotes: `A["Process (main)"]`, `-->|"O(1) lookup"|`.
7. **Sequence diagrams** — Mermaid `Note over X` / `Note left of X` / `Note right of X` are silently stripped by the renderer; don't put them in the source. If the user wants annotations on a sequence diagram, generate the base diagram first and add stickies/text via the hybrid workflow ([references/workflow.md](references/workflow.md)).
8. **Gantt charts** — `classDef`, `class`, and any other styling are stripped by preprocessing; the rendered chart will not have colors. If the user wants color-coded phases, milestones, or tasks, generate the base chart first and add color/annotations via the hybrid workflow ([references/workflow.md](references/workflow.md)) — or, for diagrams that fundamentally need styling, build the timeline directly with `use_figma` instead (see [references/gantt.md](references/gantt.md) §11).
9. **Use FigJam-only APIs in any `use_figma` extension.** `generate_diagram` output lands in a FigJam file (`figma.com/board/...`), so hybrid extensions must stick to FigJam-supported APIs. Do NOT call `figma.createPage()` — it's Design-only (`figma.com/design/...`) and throws `TypeError: figma.createPage no such property 'createPage' on the figma global object` in FigJam. Organize content with FigJam sections instead (see [figma-use-figjam](../figma-use-figjam/SKILL.md)).
## Step 4: Garbage in, garbage out
The quality of the generated diagram is bounded by the quality of the Mermaid you produce, which is bounded by the context you have. Before writing Mermaid, make sure you have enough real information to describe the subject accurately — and use whatever the current environment gives you to gather it.
Depending on what's available, useful sources of context include:
- **Source code** — grep/read the relevant files so the diagram reflects real service names, real edge labels, real data stores, real entry points. Walking actual routes/handlers/consumers beats recreating from memory.
- **User-provided documents** — a PRD, spec, meeting notes, transcript, research synthesis, onboarding doc, process write-up. Ask the user to paste or attach it if the subject isn't code.
- **Existing Figma or FigJam files** — if the new diagram should align with one the user already has, read it with `get_figjam` or `get_design_context` (see the `figma-use` and `figma-use-figjam` skills).
- **Other MCP servers or tools you have available** — issue trackers, docs sites, CRMs, analytics, internal wikis, design systems, database schemas, etc. If a connected tool holds the ground truth for what you're diagramming, pull from it rather than guessing.
- **The user themselves** — when the description is thin or ambiguous (unclear direction of flow, unclear scope, unclear which entities matter), ask one or two focused questions before generating. Examples: "What are the 3–5 main steps?", "Who owns each step?", "What triggers the next step?". One good question beats one wasted diagram.
Don't invent edges, labels, or entities to "round out" a diagram. Missing information is better than hallucinated information — leave a gap and flag it to the user.
## Step 5: Will the diagram need more than Mermaid can express?
Mermaid can't do everything. Sticky-note annotations tied to specific nodes, per-node domain coloring on ERDs, callouts with attached data — these all require composing `generate_diagram` with `use_figma` (via the [figma-use-figjam](../figma-use-figjam/SKILL.md) skill). This is the **hybrid workflow**.
It's a judgment call, not a default. Deploy it when the user's ask clearly benefits — skip it when the base diagram is obviously enough. Signals that say yes: user explicitly asked for notes, colors, callouts, or "X attached to each node"; they shared data that maps to specific nodes; the diagram is a shareable artifact, not a thinking sketch. Signals that say no: short/self-explanatory request, small diagram, user exploring or testing.
**If hybrid is warranted, read [references/workflow.md](./references/workflow.md) before calling `generate_diagram`** — it covers the pattern, two core recipes (annotations + color-coding), communication style, and failure handling. If not, proceed directly to Step 6.
## Step 6: Calling the tool
Required:
- `name`: a descriptive title (shown to the user)
- `mermaidSyntax`: the Mermaid source
Optional:
- `userIntent`: a short sentence describing what the user is trying to accomplish — helps telemetry and downstream tuning
- `useArchitectureLayoutCode`: **only for architecture diagrams**; value is specified in `references/architecture.md`
- `fileKey`: if the user wants the diagram added to an existing FigJam file instead of a new one
Do **not** call `create_new_file` before `generate_diagram` — the tool creates its own file.
## Step 7: After generation
- The tool returns a link (or widget) the user can click to open the diagram in FigJam. Show it as a markdown link unless the client renders an inline widget.
- If extensions are warranted (see Step 5), compose with `use_figma` now — the pattern and recipes are in [references/workflow.md](./references/workflow.md).
- If the user is dissatisfied after 2 attempts at the same diagram, stop regenerating. Ask what specifically is wrong, or suggest they open it in Figma and edit manually rather than burning more tool calls.
### Reuse the same file when iterating or adding related diagrams
Every call to `generate_diagram` without a `fileKey` creates a new FigJam file in the user's drafts. Regenerating 4 times = 4 draft files to clean up. Prefer reusing the existing file when:
- The user is iterating on the same diagram ("try again with…", "change the labels…").
- The user wants a follow-up diagram that lives alongside the first (e.g. a sequence diagram next to a flowchart of the same system).
How to reuse:
1. **Pass `fileKey`** on subsequent `generate_diagram` calls. Extract from a `figma.com/board/{fileKey}/...` URL. The diagram is added to the existing file rather than creating a new one.
2. If you want to replace the previous diagram rather than adding next to it, use the `use_figma` tool (see the `figma-use-figjam` skill) to delete the old diagram's nodes first, then call `generate_diagram` with the same `fileKey`. Or leave the old diagram and place the new one beside it — readers often benefit from seeing the history of attempts.
Ask the user which they prefer the first time you iterate — "regenerate over the old one, or keep both side-by-side?" — and remember their answer for subsequent iterations in the session.
Referenced files: 7
figma-generate-library24 KB
---
name: figma-generate-library
description: "Build or update a professional-grade design system in Figma from a codebase. Use when the user wants to create variables/tokens, build component libraries, create individual components with proper variant sets and variable bindings, set up theming (light/dark modes), document foundations, or reconcile gaps between code and Figma. Also use when the user asks to create or generate any component in Figma — even a single one — since components require proper variable foundations, variant states, and design token bindings to be production-quality. This skill teaches WHAT to build and in WHAT ORDER — it complements the `figma-use` skill which teaches HOW to call the Plugin API. Both skills should be loaded together."
disable-model-invocation: false
---
# Design System Builder — Figma MCP Skill
Build professional-grade design-system assets in Figma that match code. Scale the workflow to the requested deliverable: a token set, one component, a complete library, or a targeted reconciliation. Run the selected path in coherent, safely retryable construction phases with evidence-based validation.
**Prerequisites**: The `figma-use` skill MUST also be loaded for every `use_figma` call. It provides Plugin API syntax rules (return pattern, page reset, ID return, font loading, color range). This skill provides design system domain knowledge and workflow orchestration.
**Always include `figma-generate-library` in the comma-separated `skillNames` parameter when calling `use_figma` as part of this skill. If this skill was loaded via an MCP resource, you MUST prefix the name with `resource:` (e.g. `resource:figma-generate-library`).** This is a logging parameter — it does not affect execution.
---
## 1. Scope and Completion Contract
Before the first mutation, choose one primary scope from the user's request and record its included deliverables and acceptance checks. Do not silently promote a narrower request into a full-library project.
| Requested scope | Required work | Excluded unless requested or necessary |
|---|---|---|
| **Tokens / foundations only** | Requested variables, modes, scopes, code syntax, styles, and any requested specimen sheet | Components, component pages, Code Connect, full-library navigation |
| **Single component or family** | Inspect and reuse compatible foundations; create only missing tokens the component requires; build requested variants/properties/bindings; validate the component | Unrelated foundations, other components, full file skeleton, broad documentation or audits |
| **Full library / design system** | Discovery, foundations, file structure, requested component inventory, documentation, integration, and final QA | Work outside the agreed v1 inventory |
| **Reconciliation / update** | Diff code and Figma, then update only affected tokens, styles, components, docs, and dependents | Rebuilding valid assets or expanding the library |
Communicate proportionally: post one concise scope-and-acceptance checklist before mutations, give updates at meaningful boundaries, and finish with one summary of created or changed objects, validation evidence, and unresolved limitations. Do not repeat unchanged checklists or narrate every API call.
Batch related operations when the resulting script stays safe to retry. Split at page-context boundaries, hard-to-recover mutations, or for a targeted retry after an actual failure; never split a working operation only to create a validation checkpoint. Keep mutations sequential. Use structural evidence returned by writes (IDs plus relevant counts, names, and bounds), and run a separate audit only when evidence is missing or a later mutation made it stale. Take one visual review per coherent composition phase and one post-fix screenshot only after a targeted visual fix; the latest passing screenshot is final.
### Definition of done
Stop when all conditions for the chosen scope are true:
- The final deliverable covers the locked inventory and is materially faithful to the applicable source of truth, including requested variant/state coverage, required assets, and in-scope review examples.
- For component tasks, keep main components and construction assets outside the final review frame, and show the requested states as instances in one compact frame. Before stopping, inspect that frame at normal scale and repair clipped, truncated, low-contrast, empty, misplaced, or detached content.
- Required variable scopes, aliases, code syntax, component properties, variants, and bindings are verified.
- The requested artifact passes applicable structural and visual validation. Confirm requested content is fully visible unless clipping or truncation is intentional; repair defects and recheck only the affected output.
- Replace placeholders when the source provides the required asset; remove temporary instances, test frames, captures, abandoned artifacts, and unrelated stray nodes.
- No known defect or unresolved decision prevents the agreed acceptance checks from passing. Report limitations outside the scope without starting extra work.
Additional documentation, components, Code Connect mappings, accessibility sweeps, or speculative audits are follow-up work unless the chosen scope requires them. Once the definition of done passes, stop.
### Scope changes
If the user changes the deliverable, update the scope and acceptance checklist. Preserve verified work, run only newly applicable steps, and do not retroactively add work from an unselected path.
---
## 2. Scoped Workflow
Every scope begins with focused discovery: analyze the relevant code, inspect the target Figma assets and conventions, call `get_libraries` before `search_design_system`, and resolve code/Figma conflicts before mutation. Batch independent searches and reuse their results. Lock the requested inventory, then run only its path below.
### Tokens / foundations
1. Create or update the requested collections, modes, primitives, semantic aliases, scopes, and code syntax.
2. Create requested text and effect styles.
3. Create a specimen or documentation page only when requested.
4. Validate counts, mode values, aliases, scopes, syntax, styles, and any requested visual artifact; then stop.
### Single component or family
1. Inspect and reuse compatible local or library variables, text styles, and effect styles. Create only the missing foundations required by the component. For source-defined, library-standard, or repeated component roles, create or reuse a shared text or effect style and apply it to every matching component node; keep one-off documentation inline.
2. Build the requested base component, variants, properties, bindings, and dependency components.
3. Use a dedicated page when it matches the file convention or the request needs a documented showcase; otherwise place it in the existing component area.
4. Validate variant count, properties, variable bindings, applied text/effect style IDs on representative nodes, structure, and appearance; then stop.
### Full library / design system
1. **Discovery:** lock the token and component inventory and print the gap analysis.
2. **Foundations:** create collections, variables, aliases, scopes, code syntax, and styles.
3. **File structure:** create the agreed cover, getting-started, foundations, component, and utility pages plus requested documentation.
4. **Components:** build the agreed inventory in dependency order, with properties, bindings, documentation, and validation.
5. **Integration and QA:** complete Code Connect mappings for the agreed component inventory; audit accessibility, naming, and bindings; and visually validate every agreed page.
### Reconciliation / update
1. Inventory existing assets and identify exact drift from the current code source.
2. Update affected assets in place, preserving valid names, IDs, bindings, and library structure where possible.
3. Validate the changed assets and their known dependents; then stop.
If a required acceptance check fails, fix the scoped defect before continuing. Do not substitute fake assets, approximate typography, broken interactions, or unverified state.
The selected path and definition of done take precedence over broader examples or full-library phase labels in the references. Load only references needed for that path.
---
## 3. Critical Rules
**Plugin API basics** (from use_figma skill — enforced here too):
- Use `return` to send data back (auto-serialized). Do NOT wrap in IIFE or call closePlugin.
- Return ALL created/mutated node IDs in every return value
- Page context resets each call — always `await figma.setCurrentPageAsync(page)` at start. **Call it at most once per script**: each component or doc page is its own `use_figma` call. Never loop over `figma.root.children` and switch pages inside a mutating script — split that work into one focused call per target page (see [figma-use → gotchas.md → Set current page once per `use_figma` call](../figma-use/references/gotchas.md#set-current-page-once-per-use_figma-call--split-multi-page-work-across-calls))
- `figma.notify()` throws — never use it
- Colors are 0–1 range, not 0–255
- Font MUST be loaded before any text write: `await figma.loadFontAsync({family, style})`. Use `await figma.listAvailableFontsAsync()` to discover available fonts and verify exact style strings — if a load fails, query available fonts to find the correct name or a fallback.
**Design system rules**:
1. **Foundations before dependent components** — reuse compatible existing variables and styles. Create missing foundations before building a component that depends on them; do not recreate valid foundations.
2. **Inspect before creating** — run read-only `use_figma` to discover existing conventions. Match them.
3. **Match the file's component organization** — full libraries usually use one page per component; tightly related families may share a page. A single-component task may use an existing component area instead of creating a library skeleton.
4. **Bind visual properties to variables** *(default)* — fills, strokes, padding, radius, gap. Exceptions: intentionally fixed geometry (icon pixel-grid sizes, static dividers).
5. **Scopes on every variable** — NEVER leave as `ALL_SCOPES`. Background: `FRAME_FILL, SHAPE_FILL`. Text: `TEXT_FILL`. Border: `STROKE_COLOR`. Spacing: `GAP`. Radii: `CORNER_RADIUS`. Primitives: `[]` (hidden).
6. **Code syntax on every variable** — WEB syntax MUST use the `var()` wrapper: `var(--color-bg-primary)`, not `--color-bg-primary`. Use the actual CSS variable name from the codebase. ANDROID/iOS do NOT use a wrapper.
7. **Alias semantics to primitives** — `{ type: 'VARIABLE_ALIAS', id: primitiveVar.id }`. Never duplicate raw values in semantic layer.
8. **Position variants after combineAsVariants** — they stack at (0,0). Manually grid-layout + resize.
9. **INSTANCE_SWAP for icons** — never create a variant per icon. Cap variant matrices: if Size × Style × State > 30 combinations, split into sub-component.
10. **Deterministic naming** — use consistent, unique node names for idempotent cleanup and resumability. Track created node IDs via return values and the state ledger.
11. **No destructive cleanup** — cleanup scripts identify nodes by name convention or returned IDs, not by guessing.
12. **Validate from evidence before proceeding** — never build on unvalidated work. Rely on the structural evidence returned by writes (IDs plus relevant counts, names, and bounds); run one batched structural audit per coherent phase only when needed to establish the acceptance checks or when a relevant mutation invalidated prior evidence. Take one visual review per coherent composition phase, plus one post-fix screenshot only after a targeted visual fix.
13. **NEVER parallelize `use_figma` calls** — Figma state mutations must be strictly sequential. Even if your tool supports parallel calls, never run two use_figma calls simultaneously.
14. **Never hallucinate Node IDs** — always read IDs from the state ledger returned by previous calls. Never reconstruct or guess an ID from memory.
15. **Use the helper scripts** — embed scripts from `scripts/` into your use_figma calls. Don't write 200-line inline scripts from scratch.
---
## 4. State Management (Required for Long Workflows)
> Do not store workflow state on Figma objects. Use deterministic names for discovery and exact returned IDs in the state ledger. Put human-readable component purpose and usage guidance in the component or component-set `description`.
| Entity type | Stable identity | How to check existence |
|-------------|----------------|----------------------|
| Pages and frames | Deterministic name + state-ledger ID | `figma.root.children.find(p => p.name === pageName)` or `await figma.getNodeByIdAsync(id)` |
| Components and component sets | Variant/set name + state-ledger ID | `page.findOne(n => n.name === name)` or `await figma.getNodeByIdAsync(id)` |
| Variables | Name within collection | `(await figma.variables.getLocalVariablesAsync()).find(v => v.name === name && v.variableCollectionId === collId)` |
| Styles | Name | `getLocalTextStyles().find(s => s.name === name)` |
Record every returned ID in the state ledger immediately after creation. Never use a fuzzy lookup to authorize deletion.
**State persistence**: Do NOT rely solely on conversation context for the state ledger. Write it to disk:
```
/tmp/design-system-state-{RUN_ID}.json
```
Re-read this file at the start of every turn. In long workflows, conversation context will be truncated — the file is the source of truth.
Maintain a state ledger tracking:
```json
{
"runId": "ds-build-2024-001",
"scope": "single-component",
"step": "component-button",
"entities": {
"collections": { "primitives": "id:...", "color": "id:..." },
"variables": { "color/bg/primary": "id:...", "spacing/sm": "id:..." },
"pages": { "Cover": "id:...", "Button": "id:..." },
"components": { "Button": "id:..." }
},
"pendingValidations": ["Button:screenshot"],
"completedSteps": ["discovery", "foundations/verified", "component-button/base"]
}
```
**Idempotency check** before every create: query by name + state ledger ID. If exists, skip or update — never duplicate.
**Resume protocol**: at session start or after context truncation, run a read-only `use_figma` to scan all pages, components, variables, and styles by name to reconstruct the `{key → id}` map. Then re-read the state file from disk if available.
**Continuation prompt** (give this to the user when resuming in a new chat):
> "I'm continuing a design system build. Run ID: {RUN_ID}. Load the figma-generate-library skill and resume from the last completed step."
---
## 5. Library Discovery and search_design_system — Reuse Decision Matrix
Search during the scoped discovery pass and reuse the results. Search again before a component only when it was outside the original inventory, the available libraries changed, or the earlier result did not resolve it.
Before calling `search_design_system` for a target file, you MUST call `get_libraries` first for that file. You MUST NOT assume libraries are added or available.
An empty `get_libraries` result does NOT excuse skipping the search — it only means you have no library keys to scope with. `get_libraries` paginates (community UI kits appear only on the first page, org libraries page in batches of 20), so empty lists are not proof that no library exists. How to act on the result:
- **Libraries returned** — run `search_design_system` scoped with `includeLibraryKeys`. Libraries in `libraries_available_to_add` are NOT searched by default; pass their `libraryKey`s to reach them.
- **No libraries returned** — still run `search_design_system`, but omit `includeLibraryKeys`. Omitting it scopes the search to the file itself, which is exactly what you want when discovery returned nothing to scope by.
Only once the search itself comes back empty may you record "no design system assets available" in the gap analysis and build from code tokens. Never infer "no libraries" from a failed or unattempted `get_libraries` call.
```
// Discover all libraries accessible to the file
get_libraries({ fileKey })
// Returns:
// libraries_added_to_file: [{ name, libraryKey, description, source }, ...]
// libraries_available_to_add: [{ name, libraryKey, description, source }, ...]
// libraries_available_to_add_next_offset: number | null
```
Use the returned `libraryKey` values to scope searches to specific libraries via `includeLibraryKeys`. This avoids noisy results when many libraries are available.
If `libraries_available_to_add_next_offset` is non-null, more org libraries are available — call `get_libraries` again with `offset` set to that value. Org libraries page in batches of 20; community UI kits only appear on the first page.
```
// Search across all libraries (default)
search_design_system({
queries: [
{ entity: "component", query },
{ entity: "variable", query },
{ entity: "style", query }
],
fileKey
})
// Search within a specific library only
search_design_system({ queries: [{ entity: "component", query }], fileKey, includeLibraryKeys: ["lk-abc123..."] })
```
**Reuse if** all of these are true:
- Component property API matches your needs (same variant axes, compatible types)
- Token binding model is compatible (uses same or aliasable variables)
- Naming conventions match the target file
- Component is editable (not locked in a remote library you don't own)
**Rebuild if** any of these:
- API incompatibility (different property names, wrong variant model)
- Token model incompatible (hardcoded values, different variable schema)
- Ownership issue (can't modify the library)
**Wrap if** visual match but API incompatible:
- Import the library component as a nested instance inside a new wrapper component
- Expose a clean API on the wrapper
**Priority order**: local existing → subscribed library import → unsubscribed UI Kit library from `libraries_available_to_add` (icons especially) → create new.
---
## 6. Decision Forks
Ask the user when paths fork — when two or more reasonable answers exist and no clear winner comes from the codebase, the Figma file, or the locked plan. Don't silently default. Present each option with its tradeoff and your recommendation; pick only after the user steers.
**When NOT to ask:** if exactly one path is clearly correct from the source of truth (code, Figma file, agreed plan), take it. This section is for genuine ambiguity, not for offloading every decision.
| Fork situation | What to surface | Example ask |
|---|---|---|
| Code ≠ Figma on a token, component, or value | Both versions side by side, with provenance (file/line vs node) | "Code says `--color-bg-primary = #FFFFFF`, Figma has `color/bg/primary = #FAFAFA`. Which wins?" |
| Subscribed library has a close-but-not-exact match | Library component summary + gap list | "Library has `Button` with no `loading` state. Reuse + wrap locally, or rebuild from scratch?" |
| Scope ambiguity at plan-lock (0d) | What's clearly in, what's clearly out, what's ambiguous | "Spec lists `Button` and `Input`; `Field` is referenced but not defined. In or out of v1?" |
**If the user rejects an option you already built on:** fix before moving on. Never build on rejected work.
---
## 7. Naming Conventions
Match existing file conventions. If starting fresh:
**Variables** (slash-separated):
```
color/bg/primary color/text/secondary color/border/default
spacing/xs spacing/sm spacing/md spacing/lg spacing/xl spacing/2xl
radius/none radius/sm radius/md radius/lg radius/full
typography/body/font-size typography/heading/line-height
```
**Primitives**: `blue/50` → `blue/900`, `gray/50` → `gray/900`
**Component names**: `Button`, `Input`, `Card`, `Avatar`, `Badge`, `Checkbox`, `Toggle`
**Variant names**: `Property=Value, Property=Value` — e.g., `Size=Medium, Style=Primary, State=Default`
**Page separators**: `---` (most common) or `——— COMPONENTS ———`
> Full naming reference: [naming-conventions.md](references/naming-conventions.md)
---
## 8. Token Architecture
| Complexity | Pattern |
|-----------|---------|
| < 50 tokens | Single collection, 2 modes (Light/Dark) |
| 50–200 tokens | **Standard**: Primitives (1 mode) + Color semantic (Light/Dark) + Spacing (1 mode) + Typography (1 mode) |
| 200+ tokens | **Advanced**: Multiple semantic collections, 4–8 modes (Light/Dark × Contrast × Brand). See M3 pattern in [token-creation.md](references/token-creation.md) |
Standard pattern (recommended starting point):
```
Collection: "Primitives" modes: ["Value"]
blue/500 = #3B82F6, gray/900 = #111827, ...
Collection: "Color" modes: ["Light", "Dark"]
color/bg/primary → Light: alias Primitives/white, Dark: alias Primitives/gray-900
color/text/primary → Light: alias Primitives/gray-900, Dark: alias Primitives/white
Collection: "Spacing" modes: ["Value"]
spacing/xs = 4, spacing/sm = 8, spacing/md = 16, ...
```
---
## 9. Anti-Patterns
**Discovery and scope:**
- ❌ Ignoring existing conventions or skipping relevant code, file, and library discovery
- ❌ Expanding a narrow request into unrelated foundations, documentation, or a full library
**Foundations:**
- ❌ Using `ALL_SCOPES`, duplicating primitive values in the semantic layer, or omitting code syntax
- ❌ Creating dependent components before their foundations exist
**Components:**
- ❌ Hardcoding component fills, strokes, spacing, or radii when compatible variables exist
- ❌ Creating a variant per icon instead of using INSTANCE_SWAP
- ❌ Leaving variants stacked at (0,0) after `combineAsVariants`
- ❌ Building a variant matrix larger than 30 without splitting it
- ❌ Importing remote components and immediately detaching them
**Execution and recovery:**
- ❌ Retrying when `safeToRetryWithoutCanvasRead` is `false` before reading the canvas
- ❌ Using name-prefix matching for cleanup (deletes user-owned nodes)
- ❌ Building on unvalidated work from the previous step
- ❌ Parallelizing use_figma calls (always sequential)
- ❌ Guessing/hallucinating node IDs from memory (always read from state ledger)
- ❌ Writing massive inline scripts instead of using the provided helper scripts
---
## 10. Reference Docs
Read references on demand; do not infer their contents from the filename.
| Doc | Load when |
|-----|-----------|
| [discovery-phase.md](references/discovery-phase.md) | Analyzing relevant code and Figma assets before mutation |
| [token-creation.md](references/token-creation.md) | Creating variables, collections, modes, or styles |
| [documentation-creation.md](references/documentation-creation.md) | The selected scope includes cover or foundations documentation |
| [component-creation.md](references/component-creation.md) | Creating a component or variant |
| [code-connect-setup.md](references/code-connect-setup.md) | The selected scope includes Code Connect or variable code syntax |
| [naming-conventions.md](references/naming-conventions.md) | Naming variables, pages, variants, or styles |
| [error-recovery.md](references/error-recovery.md) | A script fails or abandoned workflow state needs cleanup |
---
## 11. Scripts
Reusable Plugin API helper functions. Embed in `use_figma` calls:
| Script | Purpose |
|--------|---------|
| [inspectFileStructure.js](scripts/inspectFileStructure.js) | Discover all pages, components, variables, styles; returns full inventory |
| [createVariableCollection.js](scripts/createVariableCollection.js) | Create a named collection with modes; returns `{collectionId, modeIds}` |
| [createSemanticTokens.js](scripts/createSemanticTokens.js) | Create aliased semantic variables from a token map |
| [createComponentWithVariants.js](scripts/createComponentWithVariants.js) | Build a component set from a variant matrix; handles grid layout |
| [bindVariablesToComponent.js](scripts/bindVariablesToComponent.js) | Bind design tokens to all component visual properties |
| [createDocumentationPage.js](scripts/createDocumentationPage.js) | Create a page with title + description + section structure |
| [validateCreation.js](scripts/validateCreation.js) | Verify created nodes match expected counts, names, structure |
| [cleanupOrphans.js](scripts/cleanupOrphans.js) | Remove only the exact node, variable, and collection IDs supplied from the state ledger |
Referenced files: 15
figma-generative-plugins4.74 KB
---
name: figma-generative-plugins
description: "**MANDATORY prerequisite** — load this skill before calling `create_generative_plugin` or `update_generative_plugin`. Use when the user asks to create, author, change, fix, or extend a reusable generative Figma plugin."
disable-model-invocation: false
---
# Create and update generative Figma plugins
Load this skill before every `create_generative_plugin` or `update_generative_plugin` call. In user-facing language, call the result a “plugin.” The “generative” qualifier only distinguishes this account-library tool from other plugin systems.
## Preflight
Before authoring, confirm the request fits this surface:
- Plugins run in the Figma Design editor.
- Every plugin must provide functional UI for its core workflow. A plugin with no configurable inputs still needs a clear primary action and useful status or validation feedback.
- Never embed API keys, OAuth tokens, signed URLs, or other secrets. Plugin source is readable by people who can access it. For authenticated integrations, stop and offer a static dataset, a public no-auth endpoint, or a different architecture.
## Create workflow
1. Resolve the requested workflow and its useful controls. Ask a concise question only if required inputs or behavior are genuinely ambiguous.
2. Resolve `planKey`. Reuse one supplied by the user; otherwise call `whoami`. Use the sole eligible plan automatically, or ask the user to choose when several materially different plans are available.
3. Call `create_generative_plugin` once with a concise name, description, and `planKey`. This creates a runnable square-drawing scaffold, not the requested final plugin.
4. Call `get_generative_plugin` with the returned `id`, then read every source URI. This establishes the scaffold's manifest, UI message contract, and current TypeScript entrypoint.
5. Replace the scaffold with complete authored files for the requested behavior and UI.
6. Call `update_generative_plugin` with the returned `id`, a `files` array containing complete replacements for `code.ts` and, when the UI changes, `ui.html`, plus a specific `commitMessage`. Use `metadata` when changing the name or description.
Never stop after `create_generative_plugin`: the starter must be replaced with the requested experience.
## Update workflow
1. Identify the plugin. If needed, call `list_generative_plugins`, then `get_generative_plugin`.
2. Read every source URI returned by the get tool before editing. Treat those files as the current source of truth.
3. Preserve existing UI, controls, relaunch behavior, validation, and user-visible affordances unless the user asks to change them.
4. Call `update_generative_plugin` with complete replacement content for every changed existing file. Use `{ path: "code.ts", content: "..." }` for the entrypoint and `{ path: "ui.html", content: "..." }` for the UI. Unspecified files are preserved.
## Authoring rules
- Before writing replacement files, read [Plugin source authoring](references/authoring.md). It covers the file replacement contract, UI/message lifecycle, PropsKit controls, dynamic-page compatibility, fonts, relaunch behavior, bounded work, geometry, and Plugin API gotchas.
- Keep the plugin’s primary action obvious and make invalid selection or input states understandable.
- `update_generative_plugin` can replace existing `code.ts` and `ui.html` files, but cannot replace `manifest.json` or create new files. Keep `figma.showUI(__html__, ...)` in `code.ts` and replace `ui.html` when the requested workflow needs different controls.
- Avoid destructive canvas changes unless they are the explicit purpose of the plugin and the UI makes that clear.
- Do not close before asynchronous work and UI messages have completed.
- Treat a non-error `update_generative_plugin` result as success. Record the returned version when present; a successful response may omit it.
- On a build error, use the returned compiler output to make the smallest source correction and retry once. If it still fails, surface the error instead of repeatedly rewriting the plugin.
## Completion
Report the plugin name and id, plus the returned version when present and a short description of its UI and primary action. Construct and include a clickable URL that opens a new Design file with the unpublished plugin ready to try, using the exact plugin id as `try-tool-resource-content-id`:
`https://www.figma.com/file/new?try-tool-resource-content-id=<id>&try-tool-resource-type=gen_tool&type=design&mode=design`
After presenting the new-file link, ask whether the user wants to open the plugin in an existing Figma Design file instead. If yes, reuse a file URL already provided or ask for one, then add the same `try-tool-resource-content-id` and `try-tool-resource-type` query parameters to that URL. Never guess the file URL.
Referenced files: 1
figma-implement-motion22.7 KB
---
name: figma-implement-motion
description: Translates Figma motion and animations into production-ready application code. Use when implementing animation/motion from a Figma design — user mentions "implement this motion", "add animation from Figma", "animate this component", provides a Figma URL whose node is animated, or when `get_design_context` returns motion data or instructs you to call `get_motion_context`.
disable-model-invocation: false
---
# Implement Motion
## Overview
This skill guides translation of Figma animations and transitions into runnable code (motion.dev, CSS keyframes, or framework-specific libraries).
Figma exposes motion through two tools:
- `get_motion_context` — authoritative motion tool. Returns the complete animated-node inventory, precomputed code snippets (CSS `@keyframes` + motion.dev), fallback keyframe bindings when snippets are unavailable, and recursive timeline coordination hints (`timelineCohorts`). **Source of truth for animation data and which node IDs animate.**
- `get_design_context` — the design's **structure**: layout, sizing, assets, styling, Code Connect hints, screenshot context, and sometimes **motion placement markers** on animated elements (`data-node-id`, and on split nodes `data-motion-keys` / `data-motion-wrapper-for` / `data-motion-transform-template`). It may render an animated node as a plain element (`div`, `p`, `span`, etc.) or a motion element (`motion.div`); it does not inline the animation values.
**The two are linked by node id, and that's the whole workflow.** `get_motion_context` tells you which nodes animate and gives the keyframe values, easing, timing, and snippets. `get_design_context` tells you what those nodes look like and where they sit. For every node in `get_motion_context.nodes`, find the matching `data-node-id` in design context and merge the motion into that structure — adding or wrapping a `motion.{tag}` when the structural element is plain. When design context has reused a Figma component, the motion node may also include `fallbackNodeId`; use it only as a fallback after trying the exact `nodeId`.
## Skill Boundaries
- Use this skill when the deliverable is motion code in the user's repository.
- If the user asks to create/edit animations inside Figma itself, switch to [figma-use](../figma-use/SKILL.md) and follow that skill instead.
- This skill currently covers **animations** as emitted by `get_motion_context` (snippets plus fallback keyframe tracks, including preset-authored motion resolved into those forms). Broader interactive variant flows may still need product-specific state handling in code.
## Prerequisites
- Figma MCP server connected and accessible.
- Node ID parsed from the Figma URL the user provides. URL format: `https://figma.com/design/:fileKey/:fileName?node-id=1-2` — extract `fileKey` (the segment after `/design/`) and `nodeId` (the value of the `node-id` query parameter, e.g. `42-15`).
- Target codebase. Motion output format adapts to stack (see [Framework Recommendations](#framework-recommendations)).
## Tool Choice
For motion implementation, use both tools with distinct roles:
| Situation | Tool | Why |
|---|---|---|
| Understanding static structure, assets, styles, Code Connect, or visual layout | `get_design_context` | Gives the component/page code reference and asset URLs you need to place animated nodes correctly. |
| Fetching animation data for any node | `get_motion_context` | Purpose-built for motion and the source of truth for timing, easing, snippets, and keyframes. |
| A node has motion markers (`data-motion-keys`, `data-motion-wrapper-for`) | Markers for split *placement*, `get_motion_context` for *values* | Split markers tell you which tracks go on which element; the keyframes/easing/timing and animated-node inventory come from `get_motion_context`. |
`get_motion_context` accepts `recursive: true` (capped at 500 nodes) when you need descendants' motion in one call.
## Required Workflow
### Step 1: Confirm static design context is available
```
get_design_context(fileKey=":fileKey", nodeId="<node-id>")
```
If `get_design_context` has already been called for this node, reuse that output. If not, call it normally now.
Use it as the **structure of record** — hierarchy, sizing, styling, assets, Code Connect hints, screenshot context, and any motion placement markers it happens to include (Step 3). The animated-node inventory and animation values come from `get_motion_context` (Step 2).
### Step 2: Fetch authoritative motion data
```
get_motion_context(fileKey=":fileKey", nodeId="<node-id>", recursive=true)
```
Response shape (one entry per animated node):
- `codeSnippets` — pre-generated CSS `@keyframes` and motion.dev strings. **Use these directly.** Do not regenerate them from fallback track data.
- `keyframeBindings` — bound keyframe tracks, including preset-derived motion resolved into track data, included only as fallback data when both snippet formats are missing.
- `motionSummary` — one-line-per-field natural-language description of the animation. Present **only when there's no snippet** (keyframe-bindings-only motion codegen couldn't express as CSS/motion.dev). Build from it when present; ignore it whenever a snippet exists.
- `fallbackNodeId` — optional fallback id for matching componentized design context. If `nodeId` is an instance-qualified id such as `I4005:6111;30:8005`, D2R may render the reusable component body with the backing component id instead, such as `4002:3957`. In that case, `fallbackNodeId` is the `data-node-id` to look for if exact `nodeId` lookup fails.
Recursive responses also include `timelineCohorts` — a **top-level** array (not per-node) of nodes sharing one timeline: `{ rootNodeId, durationMs, loopMode: 'once' | 'loop' | 'boomerang', memberNodeIds[] }`. For coordinated multi-node motion, drive all `memberNodeIds` from one shared lifecycle using `durationMs` (÷1000 for seconds) and `loopMode` — don't infer timing from sibling order.
Implementation details that matter for LLMs:
- When a snippet exists, `motionSummary`, `timelineDurationMs`, and `transformOrigin` may be omitted to shrink the payload — the snippet already carries duration + transform-origin (motion.dev `duration` / `style={{ transformOrigin }}`, or CSS `animation` / `transform-origin`) and the cohort carries `durationMs`. A missing field never means "no animation."
- Recursive responses dedupe exact duplicate snippets. A snippet may be replaced with a comment pointing to the first node with identical motion; reuse the same component, variant, class, or constants instead of writing a second animation.
- The MCP server infers CSS vs motion.dev snippets from `clientFrameworks`; if the response only contains one snippet format, adapt that format to the user's stack rather than assuming the other format failed.
### Step 3: Merge static and motion context
- Start from `get_motion_context.nodes`, not from visible `motion.*` tags in the static JSX. **Every returned node is animated.** Match each motion node back to `get_design_context` by exact `nodeId` / `data-node-id` first. If and only if there is no exact match, try `fallbackNodeId` / `data-node-id`. Fall back to node name/type and screenshot position only after both ids fail.
- **Exact id match wins over `fallbackNodeId`.** `fallbackNodeId` points at the backing component id that D2R may emit inside a reusable component. It is shared by every instance of that component. If the exact `nodeId` exists in design context, apply motion there and ignore the fallback. This is critical for root-instance animation: one instance can rotate or move differently from another instance of the same component, and applying that motion to the shared component body would animate all instances incorrectly.
- **Apply each motion node to the matching design-context structure, keyed by `data-node-id`.** The matching `data-node-id` is the structural anchor, not always the final DOM element that receives motion. Use the snippet shape and placement markers to decide whether motion goes on that exact element, a wrapper, an inner element, or an inlined SVG path. `get_design_context` may already emit `motion.{tag}` with values stripped, or it may emit a plain structural element (`div`, `p`, `span`, component root, etc.). If it is plain and the snippet targets the element itself, convert it to the appropriate `motion.{tag}` or add a motion wrapper while preserving the node's text, children, classes/styles, attributes, and `data-node-id`. Load [references/examples-and-anti-examples.md](references/examples-and-anti-examples.md) to see examples of this merging step.
- **Componentized child motion usually matches by fallback.** When design context extracts a Figma instance into a reusable React component, children inside that component body often have backing component ids (`4002:3957`) while motion context reports live instance ids (`I4005:6111;30:8005`). In this case, use `fallbackNodeId` to find the component-body `data-node-id`, but keep the motion scoped to the rendered instance you are implementing. If there are multiple instances and only one has different root motion, exact id matching keeps that per-instance motion separate.
- Split nodes carry a `data-motion-keys` / `data-motion-wrapper-for` marker — see _Handling interleaved transforms_ below.
- **Preserve `display: contents` wrappers — unless the group itself animates.** Layout-transparent group wrappers come through as `contents` (Tailwind `contents`), usually alongside a dead `absolute`/`inset-[…]` (those do nothing on a `contents` box). For a _static_ group, keep `display: contents` and let the children position against the nearest real ancestor — converting the wrapper's `inset` into a positioned box reparents the children to a smaller box, so they render too small / shifted inward. For an _animated_ group (the group node itself has motion), `display: contents` can't carry a transform — replace it with a real positioned wrapper and apply the group motion there. Load [references/gotchas.md](references/gotchas.md) before implementing this case.
- **`get_motion_context` is the complete animated-node inventory.** Some animated nodes render as plain (non-`motion`) elements — component instance roots (plain positioning `<div>`), text (`<p>`), masks — that still carry a `data-node-id`. Walk every node in the motion response and apply its motion to the element with the matching `data-node-id`, wrapping or converting as needed. If an animated node has no element at all in the output (e.g. an animated mask flattened into a static `mask-image`), don't drop it silently — leave a `// TODO: <nodeId> motion unsupported` comment and call it out in your summary.
- If a node appears in motion context but not in the static JSX, add the element needed to represent it — design-context code is a reference, not a complete animation inventory.
- On conflict between design and motion context (timing/easing/animated values), prefer `get_motion_context`.
- **Path-level SVG motion: inline the SVG and animate the real `<path>`.** When `get_motion_context` targets a vector's path (`PATH_TRIM`, `motion.path`, `stroke-dasharray`) but design context renders it as an `<img>`, inline the SVG and apply the snippet to the `<path>`, keeping the layout wrapper. Load [references/svg-and-path-motion.md](references/svg-and-path-motion.md) for the full how-to for this case (motion.path, `pathLength="1"`, wrapper+path layering, CSS path-trim).
#### Handling interleaved transforms
A node with **both** a static base transform and animated transforms is split across nested elements so the two compose correctly instead of fighting: an id-less `motion.div` carrying `data-motion-wrapper-for="<nodeId>"` (the OUTER wrapper) wraps a static-transform div (e.g. `rotate-45` + `hypot()` sizing — or the wrapper itself carries `data-motion-transform-template="<css>"`) which wraps the INNER node (`data-node-id`). Keep the `wrapper > static-transform div > inner` nesting — collapsing it breaks sizing and the base transform.
- **Place tracks by `data-motion-keys`.** The wrapper's `data-motion-keys` (transform tracks — `x`/`y`/`rotate`/`scaleX`/`scaleY`/`skewX`) go on the OUTER wrapper; the inner element's `data-motion-keys` go on the INNER element.
- **Re-apply a `data-motion-transform-template`.** If the wrapper carries one, set `transformTemplate={(_, generated) => "<css> " + generated}` so the animated transform composes on top of that static layout transform.
- **Offset the animated transform by the static base (avoid double rotation).** `get_motion_context` gives the node's _absolute_ transform, which already includes whatever static base those divs apply. A `rotate` snippet of `[45, 125, 125]` over a `rotate-45` base means the wrapper animates the **offset** `[0, 80, 80]` (= absolute − 45), not the absolute — else the 45° applies twice and the element sits at 90° at rest. Tracks with no static base (e.g. `x`/`y` starting at 0) pass through unchanged. See the interleaved-transform example.
- **Keep layout transforms separate from Motion transforms.** For every `motion.*` element that animates `rotate`, `scale`, or `skew`, verify it does not also rely on Tailwind layout transforms such as `-translate-x-1/2` or `-translate-y-1/2` for centering/positioning. Those utilities share the CSS `transform` property that Motion.dev writes inline, so Motion's transform can erase the layout translate. If both are needed, split the element into a static layout wrapper carrying the centering/positioning transform and an inner `motion.*` element carrying animated rotate/scale/opacity, or encode the layout offset in Motion itself (`x: "-50%"`) and keep it present for every keyframe.
### Step 4: Apply the motion in code
- **motion.dev present in snippets?** Use the motion.dev code verbatim for React targets. Import from `motion/react` — unless the codebase already uses another motion library (Framer Motion, React Spring, GSAP), in which case adapt the snippet to it. Load [references/framework-recommendations.md](references/framework-recommendations.md) when adapting to another stack or choosing a library.
- **CSS keyframes present?** Use for vanilla/non-React targets, or when the codebase has no React motion library.
- **No snippets (keyframe-bindings-only)?** Build equivalent motion.dev/CSS from `keyframeBindings` + `motionSummary`, taking loop timing from the cohort's `durationMs` / `loopMode` and reading `transformOrigin` / duration from the structured fields. Rare — snippets are normally present, including for SwiftUI/iOS (which get the CSS format).
### Step 5: Validate
- Read the component's existing motion imports/conventions before adding new ones. If the user already uses Framer Motion / React Spring / anime.js, adapt rather than forcing motion.dev.
- Spot-check one animation runs end-to-end (reload, observe, iterate) before batching changes across many nodes.
- Load [references/gotchas.md](references/gotchas.md), which covers specific bugs and edge cases seen in Figma motion output, and correct any such cases in the generated code.
## Critical Rules
These are the general principles. Specific gotchas (rotation pivots, HOLD semantics, color interpolation, etc.) live in the categorized [references](#references). When a linked reference is mentioned in this skill text and the situation applies, load that file before continuing.
1. **Respect the tool's output's *values*, not its layout.** Preserve the exact timing, easing, keyframe values, and `transformOrigin` from `codeSnippets` — don't regenerate them from `keyframeBindings` or the structured fields when snippets exist (regenerating loses fidelity on custom bezier easings, spring approximations, and overshoot values). `transformOrigin` is **per element**: apply each scaling/rotating node's own — including nested scalers, not just the outer wrapper — or the element pivots from the default center and grows/spins from the wrong corner (see the per-element-`transformOrigin` example). But the snippet is one node's data, not a copy-paste template: when many nodes share it, factor it per Rule 7 instead of pasting the block N times.
2. **Match the user's existing motion stack.** Read the component's imports and any sibling animations before adding dependencies. If the user already has Framer Motion, React Spring, anime.js, GSAP — adapt the output to their stack rather than forcing motion.dev.
3. **Honor `prefers-reduced-motion`.** Any motion added must soften or disable under `@media (prefers-reduced-motion: reduce)` — typically skip the `animate` (render the initial/resting state) or cut the duration to near-zero. This is an accessibility default, not an opt-in.
4. **Validate one animation end-to-end before batching.** Build, reload, and watch one full timeline loop — confirm each animated node appears at the time its keyframe track says it should. "Renders without error" is not "renders correctly." Motion failures compound when you batch — a wrong easing on one node is easy to spot; the same bug across twenty nodes is hours of untangling.
5. **Don't fabricate motion.** If a node has no motion data in the response, leave it static. Do not borrow easing/duration defaults from elsewhere in the design, and do not auto-animate "because the rest of the component is animated."
6. **Don't download an asset just to `Read` it.** `get_design_context` / `get_motion_context` return assets as URLs (`/api/mcp/asset/...`), often SVG. Reference the URL directly where the consumer fetches it (an `<img src>`, CSS `background-image`, an asset import), or `curl` one to inline its contents (e.g. inline the SVG and render via `NSImage(data:)` on SwiftUI). The important exception is path-level SVG motion: if the motion snippet targets a path inside an SVG asset, inline the SVG and animate the real path instead of leaving it behind an `<img>`. Don't download an asset and feed the file to the `Read` tool: SVG isn't a Read-able image format, so the read is rejected and wasted — and a file tool that doesn't detect SVG-as-image can stall the loop on it.
7. **Factor out repeated motion — never copy-paste the snippet per element.** Many nodes usually share the *same* animation differing only by a stagger delay, offset, or target value. Implement the shared motion **once** — a reusable animated component or a `variants` object parameterized by the values that vary — render from a mapped array (`items.map(...)`), and pull repeated literals (durations, easing arrays, offsets) into named constants. The animation's *values* stay verbatim from the snippet (Rule 1); the *code* stays DRY. The same transition object pasted 15+ times (800 lines that should be 150) is a low-quality result — fidelity and maintainability are both graded.
## Framework Recommendations
Rule 2 covers the general posture: prefer the user's existing stack. When none exists, defaults:
- **React**: [motion.dev](https://motion.dev) (the `motion` package). The tool returns motion.dev code directly — use it.
- **Vanilla / non-React web**: CSS `@keyframes` with `animation` shorthand, returned directly by the tool.
- **SwiftUI**: Native `.animation(...)` modifiers, translated from the **CSS** snippet (`get_motion_context` emits no SwiftUI code, but SwiftUI/iOS clients still get the CSS format; fall back to `keyframeBindings` / `motionSummary` / cohort only when snippet-less). **Use only real SwiftUI APIs** — no modifier takes a Figma/CSS easing directly, so load [references/framework-recommendations.md](references/framework-recommendations.md#swiftui-translation), map the easing to its SwiftUI equivalent, and verify rather than invent. This path is evolving; confirm with the user if unsure.
**For established effect classes, prefer a library over hand-rolled CSS.** Effects like glass/glassmorphism, confetti, particle systems, physics-based interactions, and scroll-linked motion have battle-tested library implementations that handle cross-browser quirks, accessibility, and performance far better than generated keyframes. Load [references/framework-recommendations.md](references/framework-recommendations.md) for the full library-by-effect-class table. Surface these as recommendations, not mandates — the user decides.
## Examples
Load [references/examples-and-anti-examples.md](references/examples-and-anti-examples.md) when you need worked examples or failure patterns. It covers the simple merge flow, plain text elements that need `motion.*` added, interleaved static+animated transforms, SVG path-level motion, and anti-examples for DOM rebuilding, node-id/position drift, and missing per-element `transformOrigin`.
## References
Six deep dives, fetched on demand. General frontend concerns (performance, units, accessibility mechanics) are handled by the critical rules above — these references focus on Figma-specific signal only. If this skill names one of these files in an inline instruction, load that file before continuing with that part of the task.
- [references/examples-and-anti-examples.md](references/examples-and-anti-examples.md) — worked examples and failure patterns. Load when applying the merge workflow, handling interleaved transforms, or checking whether a generated implementation has rebuilt the DOM, swapped node positions, or dropped `transformOrigin`.
- [references/gotchas.md](references/gotchas.md) — Figma-specific motion bugs and their fixes. Rotation/scale origin on nested groups, HOLD easing semantics, CUSTOM_SPRING preservation, independent axis scaling ambiguity, color interpolation. Load when troubleshooting unexpected runtime behavior. **Always load [references/motion-lint-rules.md](references/motion-lint-rules.md) alongside this file** — gotcha entries reference specific lint rules that must be surfaced to the user.
- [references/svg-and-path-motion.md](references/svg-and-path-motion.md) — implementing motion that targets an SVG vector path (inline the asset, `motion.path`, `pathLength="1"`, wrapper+path layering, CSS path-trim). Load when a vector's snippet targets the path, not a wrapper transform.
- [references/framework-recommendations.md](references/framework-recommendations.md) — motion.dev, CSS keyframes, SwiftUI defaults, library-by-effect-class table (glass, confetti, particles, physics, scroll-linked). Load before hand-rolling an effect.
- [references/unsupported-and-fallbacks.md](references/unsupported-and-fallbacks.md) — Figma motion features that don't export cleanly today (text animations, path animations, masks/booleans, variants/transitions). Includes video/lottie fallback guidance. Load when the tool response seems incomplete. **Always load [references/motion-lint-rules.md](references/motion-lint-rules.md) alongside this file** — unsupported entries reference specific lint rules that must be surfaced to the user.
- [references/motion-lint-rules.md](references/motion-lint-rules.md) — Linting rules: known export limitations (errors and warnings) that must be surfaced to the user. Load when generating motion code to check whether any active limitations apply.
Referenced files: 6
figma-shaders5.48 KB
---
name: figma-shaders
description: "**MANDATORY prerequisite** — load this skill before calling `create_shader` or `update_shader`. Use when the user asks to create, author, change, fix, or iterate on a shader effect, shader fill, custom effect, custom fill, or procedural shader in Figma."
disable-model-invocation: false
---
# Create and update Figma shaders
Load this skill before every `create_shader` or `update_shader` call. It covers account-library shader authoring through the Figma MCP server. Reading or applying an existing shader does not require this skill.
Shaders have two kinds:
- `effect` samples and transforms the rendered layer beneath it. Use it for blur, distortion, glow, color grading, pixelation, halftone, or other post-processing.
- `fill` generates pixels without an input raster. Use it for gradients, patterns, noise, textures, and procedural backgrounds.
Do not silently switch kinds during an update. The `kind` passed to `update_shader` must match the existing shader.
## Create workflow
1. Decide whether the request is an effect or fill. Ask only when the visual intent does not resolve the distinction.
2. Resolve `planKey`. Reuse one supplied by the user; otherwise call `whoami`. Use the sole eligible plan automatically, or ask the user to choose when several materially different plans are available.
3. Call `create_shader` once with a concise name, description, selected `kind`, and `planKey`. This creates a starter scaffold, not the requested final shader.
4. Call `get_shader` with the returned `id`, then read every source URI. If the client cannot read MCP resources, call `get_shader` with `includeSource: true` instead. This establishes the runtime imports and the scaffold's fixed metadata contract.
5. Author the complete replacement `main.ts` for the requested result.
6. Call `update_shader` with the returned `id`, the same `kind`, `files: [{ path: "main.ts", content: "..." }]`, and a specific `commitMessage`. Use `metadata` when changing the name, description, or animation capabilities. Set `metadata.isAnimated: true` when the source reads time-related frame inputs and `metadata.usesMouse: true` when it reads `frame.mousePosition`.
Never stop after `create_shader`: the starter scaffold is only a structural starting point.
## Update workflow
1. Identify the shader with `list_shaders`, then call `get_shader`. Use its `type` (`effect` or `fill`) as the required update kind.
2. Read every source URI returned by `get_shader` before editing. If the client cannot read MCP resources, call it with `includeSource: true`. Treat those files as the current source of truth.
3. Preserve existing controls and behavior unless the user asks to change them.
4. Call `update_shader` with the complete replacement `main.ts` in the `files` array, not a diff or partial fragment. An empty `files` array is valid only for a metadata-only update.
## Authoring rules
- Before writing the `main.ts` replacement, read [Shader source authoring](references/authoring.md). It contains the required module shape, WebGPU lifecycle, supported parameter schemas, effect/fill alpha rules, and WGSL failure checklist.
- Match animation metadata to the source. Set `metadata.isAnimated: true` if `main.ts` reads `frame.time`, `frame.deltaTime`, or `frame.frame`, and set `metadata.usesMouse: true` if it reads `frame.mousePosition`. Set the corresponding value to `false` when removing the last such use. `update_shader` applies these metadata fields to the fixed `features.json` manifest even though that file cannot be replaced directly.
- Prefer `frame.time` for animation so skipped frames do not change the result. It is an absolute millisecond clock; convert it to seconds with `Number(frame.time) * 0.001` when useful.
- Expose controls for values users are likely to tune per layer; hardcode implementation details.
- Keep numeric ranges bounded and defaults visually useful.
- For effects, sample the input raster intentionally. For fills, do not assume an input raster exists.
- Treat a non-error `update_shader` result as success. Record the returned version when present; a successful response may omit it.
- On a build error, use the returned compiler output to make the smallest source correction and retry once. If it still fails, surface the error instead of repeatedly rewriting the shader.
- If the tool reports that animation or mouse input is unavailable, treat that as a terminal capability gate: do not retry or attempt to bypass it. Offer to author a static shader whose exposed properties can be keyframed in Motion mode instead.
## Completion
Report the shader name, kind, and id, plus the returned version when present. Briefly identify the controls or behavior that were added. Construct and include a clickable URL that opens a new Design file with the unpublished shader ready to try, using the exact shader id as `try-tool-resource-content-id`. Set `try-tool-resource-type` from the shader kind: `gen_effect` for an effect and `gen_fill` for a fill.
`https://www.figma.com/file/new?try-tool-resource-content-id=<id>&try-tool-resource-type=<gen_effect|gen_fill>&type=design&mode=design`
After presenting the new-file link, ask whether the user wants to open the shader in an existing Figma Design file instead. If yes, reuse a file URL already provided or ask for one, then add the same `try-tool-resource-content-id` and resolved `try-tool-resource-type` query parameters to that URL. Never guess the file URL.
Replace the type placeholder with exactly one value; do not include angle brackets or the pipe in the returned URL.
Referenced files: 1
figma-swiftui3.95 KB
---
name: figma-swiftui
description: "SwiftUI ↔ Figma translation. Use whenever the user mentions Swift, SwiftUI, iOS, iPhone, or iPad — in EITHER direction — translating a Figma design into SwiftUI (design → code), or pushing SwiftUI views / screens / tokens back into a Figma file (code → design). Triggers on phrases like 'implement this Figma design in SwiftUI', 'build this screen in Swift', 'push this SwiftUI view to Figma', 'mirror my Swift code in a Figma file', or whenever a Figma URL appears alongside `.swift` files / an `.xcodeproj`. Routes to a direction-specific reference doc; loads alongside `figma-use` for the code → design path."
disable-model-invocation: false
---
# Figma ↔ SwiftUI
Translation between Figma designs and SwiftUI code, both directions. This file is a router — actual guidance lives in the references below.
## Pick the direction
| Direction | Trigger | Reference |
|---|---|---|
| **Design → code** | User wants SwiftUI in their iOS project from a Figma file/frame | [references/design-to-code.md](references/design-to-code.md) |
| **Code → design** | User wants to push SwiftUI views / screens / tokens into a Figma file | [references/code-to-design.md](references/code-to-design.md) |
If the request is ambiguous — a Figma URL and `.swift` files both present, no verb makes it clear — ask the user which direction before loading a reference.
## Shared context (applies to both directions)
These points hold regardless of direction; the direction-specific references assume them.
1. **`get_design_context` is the read tool for Figma.** Pass `clientLanguages: "swift"` and `clientFrameworks: "swiftui"` so the response is framed as Swift. URL → tool args: `figma.com/design/:fileKey/:fileName?node-id=:nodeId` → use `fileKey`, replace `-` with `:` in `nodeId`. For `figma.com/design/:fileKey/branch/:branchKey/:fileName`, use `branchKey` as `fileKey`.
2. **The React+Tailwind in `get_design_context` output is a structural reference, not a literal source.** It approximates the visual. Never transliterate `position: absolute` / pixel frames / `mix-blend-mode` stacks into SwiftUI or into Figma — the screenshot is the source of truth in both directions.
3. **iOS HIG semantic colors are tokens, not hex.** `var(--backgrounds/primary, …)`, `var(--labels/secondary, …)`, `var(--separators/non-opaque, …)` etc. map to `Color(.systemBackground)`, `Color.secondary`, `Color(.separator)` in SwiftUI, and to variables in a semantic collection in Figma. Keep the mapping; drop the literal RGBA.
4. **SF Symbols round-trip by name in both directions — never by codepoint.** Design → code: `get_design_context` substitutes Figma's SF Symbol glyph runs back into `<SFSymbol>{Image(systemName: "...")}</SFSymbol>` wrappers in the response. Use those names verbatim. Code → design: call `figma.util.getSfSymbolCharacter(name)` inside `use_figma` to convert a symbol name to the matching character — never look up codepoints by hand.
5. **Recognize the underlying iOS pattern, not the literal node / view name.** The same patterns recur in both directions: large title + back chevron + trailing action = `NavigationStack` chrome; bottom row of icon+label pairs = `TabView`; repeating same-height rows with leading/trailing chrome = `List`. Match those system patterns rather than rebuilding them from primitives.
6. **For code → design, `use_figma` is the API.** Always load [`figma-use`](../figma-use/SKILL.md) before any `use_figma` call. If the task involves building a full screen, also load [`figma-generate-design`](../figma-generate-design/SKILL.md); if it involves building components or a design system, also load [`figma-generate-library`](../figma-generate-library/SKILL.md).
## References
| Doc | When to load |
|---|---|
| [references/design-to-code.md](references/design-to-code.md) | Translating a Figma design / frame into SwiftUI |
| [references/code-to-design.md](references/code-to-design.md) | Pushing SwiftUI views / screens / tokens into Figma |
Referenced files: 2
figma-use33.1 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."
disable-model-invocation: false
---
# use_figma — Figma Plugin API Skill
Use the `use_figma` tool to execute JavaScript in Figma files via the Plugin API. All detailed reference docs live in `references/`.
**Always include `figma-use` in the comma-separated `skillNames` parameter when calling `use_figma`. If this skill was loaded via an MCP resource, you MUST prefix the name with `resource:` (e.g. `resource:figma-use`).** This is a logging parameter used to track skill usage — it does not affect execution.
**If Figma MCP tools appear as deferred tools, batch-load all their schemas in a single `ToolSearch` call** using the `select:` syntax — e.g. `ToolSearch query="select:use_figma,get_screenshot,get_metadata,create_new_file"`. One round trip beats six.
**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.
**If the task involves creating or building a component in Figma** (even a single component), also load [figma-generate-library](../figma-generate-library/SKILL.md). It provides the component creation workflow — variable foundations, variant sets, design token bindings — that `figma-use` alone doesn't cover.
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. **Return node IDs and keep workflow state outside the Figma file.** Set human-readable component purpose and usage in `node.description` only on a `COMPONENT` or `COMPONENT_SET` — never on a frame or instance.
3b. **Narrow before accessing type-specific properties.** Check `node.type`, use a capability guard such as `"characters" in node`, or prefilter with `findAllWithCriteria`. `characters` requires a text-capable node; optional chaining does not protect unsupported property access.
4. `console.log()` is NOT returned — use `return` for output
5. **Size construction calls for safe retry and validate from evidence.** Do not split a working operation solely to create validation checkpoints. Batch related work when the resulting script remains safe to retry; a complete section or page may be built in one call. Split when crossing page context, when partial execution would be difficult to recover, or after an actual failure requires a targeted retry. Return affected IDs and relevant counts, names, or bounds from each write—this counts as structural validation. Run a separate structural check only when required evidence is missing or after a relevant mutation. Normally take one screenshot after composition and one after a visual fix. The most recent passing screenshot is the final check; do not repeat it when nothing relevant changed. Stop once requirements pass.
6. Colors are **0–1 range** (not 0–255): `{r: 1, g: 0, b: 0}` = red. Paint `color` objects use `{r, g, b}` **only — no `a` field**; opacity goes at the paint level (`{ type: 'SOLID', color: {...}, opacity: 0.5 }`).
7. Fills/strokes are **read-only arrays** — clone, modify, reassign
8. **Every text edit follows the canonical recipe: load font → `await` → mutate → return affected node IDs.** Skipping the load throws `Cannot write to node with unloaded font "<family> <style>"`. The rule covers more than `characters` — it applies to any operation on nodes with unloaded fonts (`appendChild`, `insertChild`, `setBoundVariable`, `setExplicitVariableModeForCollection`, `setValueForMode`, `findAll` callbacks touching text). When mutating existing text, load the node's *current* fonts via `getStyledTextSegments(['fontName'])`, not a hardcoded default. Inter is preloaded in most environments so other families surface this bug more often — the recipe is the same for every font. Use `await figma.listAvailableFontsAsync()` first if the style string is unverified — **never guess** (`"SemiBold"` vs `"Semi Bold"` is a common footgun). For `FONT_FAMILY`-scoped variables, load every value across every relevant mode before `setBoundVariable("fontFamily", …)`, `setValueForMode`, or `setExplicitVariableModeForCollection`. `lineHeight`/`letterSpacing` take `{unit, value}`, not bare numbers. See [Canonical text-edit recipe](references/gotchas.md#canonical-text-edit-recipe-font-load--await--mutate--return-ids).
9. **Pages load incrementally** — use `await figma.setCurrentPageAsync(page)` to switch pages and load their content. The sync setter `figma.currentPage = page` does **NOT** work and throws `"Setting figma.currentPage is not supported"`. Page context resets to the **first page** at the start of every `use_figma` call, so re-switch each call; switch **at most once per call** and fan multi-page work out in parallel — see [Page Rules](#2-page-rules-critical) and [gotchas.md](references/gotchas.md#set-current-page-once-per-use_figma-call--split-multi-page-work-into-parallel-calls).
10. `setBoundVariableForPaint` returns a **NEW** paint — must capture and reassign
11. `createVariable` accepts collection **object or ID string** (object preferred)
12. **`layoutSizingHorizontal/Vertical` is value-restricted by structural context — `FIXED` always works, `HUG` and `FILL` do not.** `'HUG'` is valid only on an auto-layout frame itself OR on a **TEXT** child of one. `'FILL'` is valid only on a child of an auto-layout frame that is also not absolute-positioned, not inside an immutable frame, and not a canvas-grid child. Practical consequence: append to an auto-layout parent FIRST, then set `HUG`/`FILL` — a newly-created or unparented node can't satisfy the rule yet. The property itself exists on every `SceneNode`; the error is value-rejection, not "no such property". See [Gotchas](references/gotchas.md#layoutsizinghorizontallayoutsizingvertical-value-rules-fixed-hug-fill).
12a. **Use auto-layout for containers that hold related children.** When children have a structural relationship — stacked, side-by-side, aligned, gapped, hugged — wrap them in `figma.createAutoLayout()`, not `figma.createFrame()` with absolute `x`/`y`. Absolute coordinates govern where a container sits on the canvas; auto-layout governs how its children relate inside it. Skipping the container leaves no protection against text reflow, content changes, or overlap.
12b. **`layoutSizing*` and `*AxisSizingMode` are different enums — don't cross them.** `layoutSizingHorizontal`/`layoutSizingVertical` (set on a **child**) take `'FIXED'|'HUG'|'FILL'`; `primaryAxisSizingMode`/`counterAxisSizingMode` (set on the **frame** itself) take `'FIXED'|'AUTO'`. So `layoutSizingVertical = 'AUTO'` is invalid (use `'HUG'`), and `counterAxisSizingMode = 'FILL'` throws `Expected 'FIXED' | 'AUTO', received 'FILL'` (use `'FIXED'`/`'AUTO'`). Two more errors from the same setter — `Error: in set_layoutSizingHorizontal: node must be an auto-layout frame or a child of an auto-layout frame` and `Error: in set_layoutSizingHorizontal: FILL can only be set on children of auto-layout frames` — mean the node isn't in an auto-layout context yet; **recommendation: make the parent auto-layout (`figma.createAutoLayout()`) and `appendChild` the node before setting** (see Rule 12). See [Gotchas](references/gotchas.md#layoutsizing-vs-axissizingmode-two-different-sizing-enums).
12c. **`resize()` resets sizing modes to `FIXED`, so call it BEFORE setting `layoutSizing*`.** A wrapping **TEXT** block needs `textAutoResize = 'HEIGHT'` plus an explicit FIXED width (`resize()`), NOT `FILL` alone — the default `WIDTH_AND_HEIGHT` mode ignores `FILL` and collapses the node to a near-zero-width thread. Verify `node.width > 0` afterward.
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, obey `safeToRetryWithoutCanvasRead`.** If `true`, fix the error and retry. If `false`, read the canvas, determine what changed, then make changes. See [Error Recovery](#7-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. No state persists across `use_figma` calls, so pass IDs from previous calls as **string literals**, not variables. See [common-patterns.md](references/common-patterns.md) for worked multi-step examples.
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.
18. **Never read `componentPropertyDefinitions` from a variant component.** Narrow the owner first: use the node itself when it is a `COMPONENT_SET`, use a `COMPONENT` only when its parent is not a `COMPONENT_SET`, and otherwise promote a variant `COMPONENT` to its parent set. Optional chaining does not make the getter safe. See [Component-property owner narrowing](references/component-patterns.md#component-property-owner-narrowing).
> For detailed WRONG/CORRECT examples of each rule, see [Gotchas & Common Mistakes](references/gotchas.md).
## 2. Page Rules (Critical)
The async-only setter and the per-call context reset are covered in Rule 9. This section elaborates the one rule that needs a worked example: **switch pages at most once per script, and fan multi-page work out in parallel.**
Never loop over `figma.root.children` and switch pages inside the loop — each switch reloads the file. If the work spans multiple pages, **split it into N `use_figma` calls (one per target page) and emit them in parallel** — a single assistant message containing N `use_figma` tool-use blocks. The harness runs them concurrently; each script sets `currentPage` exactly once.
> **Explicit instruction:** when fanning out, you MUST issue the N tool calls in **one message**. Do not send them across multiple turns. Do not await one before issuing the next. Sequential per-page calls waste the entire benefit of splitting.
```js
// AVOID — switches pages N times in one script, reloads the file each time
for (const page of figma.root.children) {
await figma.setCurrentPageAsync(page);
// ... touch this page ...
}
// PREFER — read-only discovery call to get page IDs, then in the NEXT message
// emit N parallel use_figma tool calls (one per page), each setting currentPage once.
```
Default to parallel fan-out for any multi-page work — reads and writes alike. You can also call `use_figma` multiple times to build incrementally (e.g. `return` metadata about existing nodes, then modify them in a subsequent script). See [gotchas.md → Set current page once per `use_figma` call](references/gotchas.md#set-current-page-once-per-use_figma-call--split-multi-page-work-into-parallel-calls) for the full rationale.
## 3. `return` Is Your Output Channel
The agent sees **ONLY** the value you `return` — `console.log()` is invisible (Rule 4), and thrown errors are auto-captured (let them propagate or `throw` explicitly). The hard requirement is Rule 15: every script that creates or mutates canvas nodes **MUST** return all affected node IDs plus any actionable status in a structured object — e.g. `return { createdNodeIds: [...], mutatedNodeIds: [...], count: 5, errors: [] }` — so subsequent calls can reference, validate, or clean them up.
## 4. Editor Mode
`use_figma` works in **design mode** (editorType `"figma"`, the default). FigJam (`"figjam"`) and Slides (`"slides"`) have different sets of available node types — most design nodes are blocked in FigJam, and FigJam-only nodes are blocked in Slides.
**Tell the editor from the URL:** Design = `figma.com/design/...`, FigJam = `figma.com/board/...`, Slides = `figma.com/slides/...`. Confirm before assuming an API is available.
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, SlideGrid, InteractiveSlideElement, Webpage.
Available in Slides mode: Rectangle, Frame, Component, Text, Ellipse, Star, Line, Vector, Polygon, BooleanOperation, Slice, Section, TextPath, Slide, SlideRow, SlideGrid, InteractiveSlideElement.
**Blocked** in Slides mode: Sticky, Connector, ShapeWithText, CodeBlock, Webpage, Page.
**Design-only APIs (not just node types):** `figma.createPage()` is available only in Design files (`figma.com/design/...`). In both FigJam (`figma.com/board/...`) and Slides (`figma.com/slides/...`) it throws `TypeError: figma.createPage no such property 'createPage' on the figma global object`. Do not emit `figma.createPage()` in FigJam or Slides workflows.
> **Slides note:** There is no dedicated read tool for Slides files yet. Use `use_figma` with read-only scripts for inspection (see Section 6 "Inspect first" pattern), and `get_screenshot` / `await node.screenshot()` for visual context. For Slides-specific API guidance, load the [figma-use-slides](../figma-use-slides/SKILL.md) skill.
## 5. Efficient APIs — Prefer These Over Verbose Alternatives
These APIs reduce boilerplate, eliminate ordering errors, and compress token output. **Always prefer them over the verbose alternatives.**
### `node.query(selector)` — CSS-like node search
Find nodes within a subtree using CSS-like selectors. Replaces verbose `findAll` + filter loops.
```js
// BEFORE — verbose traversal
const texts = frame.findAll(n => n.type === 'TEXT' && n.name === 'Title')
// AFTER — one-liner with query
const texts = frame.query('TEXT[name=Title]')
```
**Selector syntax:**
- Type: `FRAME`, `TEXT`, `RECTANGLE`, `ELLIPSE`, `COMPONENT`, `INSTANCE`, `SECTION` (case-insensitive)
- Attribute exact: `[name=Card]`, `[visible=true]`, `[opacity=0.5]`
- Attribute substring: `[name*=art]` (contains), `[name^=Header]` (starts-with), `[name$=Nav]` (ends-with)
- Dot-path traversal: `[fills.0.type=SOLID]`, `[fills.*.type=SOLID]` (wildcard index)
- Instance matching: `[mainComponent=nodeId]`, `[mainComponent.name=Button]`
- Combinators: `FRAME > TEXT` (direct child), `FRAME TEXT` (any descendant), `A + B` (adjacent sibling), `A ~ B` (general sibling)
- Pseudo-classes: `:first-child`, `:last-child`, `:nth-child(2)`, `:not(TYPE)`, `:is(FRAME, RECTANGLE)`, `:where(TEXT, ELLIPSE)`
- Node ID: `#nodeId` or bare GUID
- Comma: `TEXT, RECTANGLE` (union)
- Wildcard: `*` (any type)
**QueryResult methods:**
| Method | Description |
|---|---|
| `.length` | Number of matched nodes |
| `.first()` | First matched node (or `null`) |
| `.last()` | Last matched node (or `null`) |
| `.toArray()` | Convert to regular array |
| `.each(fn)` | Iterate with callback, returns `this` for chaining |
| `.map(fn)` | Map to new array |
| `.filter(fn)` | Filter to new QueryResult |
| `.values(keys)` | Extract property values: `.values(['name', 'x', 'y'])` → `[{name, x, y}, ...]` |
| `.set(props)` | Set properties on all matched nodes (see `node.set()` below) |
| `.query(selector)` | Sub-query within matched nodes |
| `for...of` | Iterable — works in `for` loops |
**Scope:** `node.query()` searches within that node's subtree. To search the whole page: `figma.currentPage.query('...')`. There is no global `figma.query()`.
**Examples:**
```js
// Recolor all text inside cards
figma.currentPage.query('FRAME[name^=Card] TEXT').set({
fills: [{type: 'SOLID', color: {r: 0.2, g: 0.2, b: 0.8}}]
})
// Get names and positions of all frames
return figma.currentPage.query('FRAME').values(['name', 'x', 'y'])
// Find the first component named "Button"
const btn = figma.currentPage.query('COMPONENT[name=Button]').first()
// Find all instances of a specific component
figma.currentPage.query(`INSTANCE[mainComponent=${compId}]`)
// Find nodes with solid fills using dot-path traversal
figma.currentPage.query('[fills.0.type=SOLID]')
```
### `node.set(props)` — batch property updates
Set multiple properties in one call. Returns `this` for chaining.
```js
// BEFORE — one line per property
frame.opacity = 0.5
frame.cornerRadius = 8
frame.name = "Card"
// AFTER — single call
frame.set({ opacity: 0.5, cornerRadius: 8, name: "Card" })
```
**Priority key ordering:** `layoutMode` is always applied before other properties (like `width`/`height`) regardless of object key order. This prevents the common bug where `resize()` behaves differently depending on whether `layoutMode` is set.
**Width/height handling:** `width` and `height` are routed through `node.resize()` automatically — setting `{ width: 200 }` calls `resize(200, currentHeight)`.
**Chaining with query:**
```js
// Find all rectangles named "Divider" and update them
figma.currentPage.query('RECTANGLE[name=Divider]').set({
fills: [{type: 'SOLID', color: {r: 0.9, g: 0.9, b: 0.9}}],
cornerRadius: 2
})
```
### `figma.createAutoLayout(direction?, props?)` — auto-layout frames
Creates a frame with auto-layout already enabled and both axes hugging content. **This is the default container whenever children have a structural relationship to each other (see Rule 12a).**
```js
// BEFORE — manual setup, easy to get ordering wrong
const frame = figma.createFrame()
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO'
frame.counterAxisSizingMode = 'AUTO'
frame.layoutSizingHorizontal = 'HUG'
frame.layoutSizingVertical = 'HUG'
// AFTER — one call, layout ready
const frame = figma.createAutoLayout('VERTICAL')
```
Children can immediately use `layoutSizingHorizontal/Vertical = 'FILL'` after being appended — no need to set sizing modes manually.
Accepts an optional props object as the first or second argument:
```js
figma.createAutoLayout({ name: 'Card', itemSpacing: 12 }) // HORIZONTAL + props
figma.createAutoLayout('VERTICAL', { name: 'Column', itemSpacing: 8 }) // VERTICAL + props
```
### `node.placeholder` — shimmer overlay for AI-in-progress feedback
Sets a visual shimmer overlay on a node indicating work is in progress. **Always remove the shimmer when done** — leftover shimmers confuse users and indicate incomplete work.
```js
// Mark as in-progress
frame.placeholder = true
// ... build out the content ...
// MUST remove when done — never leave shimmers on finished nodes
frame.placeholder = false
```
When building complex layouts, set `placeholder = true` on sections before populating them, then set `placeholder = false` on each section as it's completed.
### `await node.screenshot(opts?)` — inline screenshots
Capture a node as a PNG and return it inline in the response. Eliminates the need for a separate `get_screenshot` call.
```js
// Take a screenshot of a frame (returned inline in the tool response)
await frame.screenshot()
// Custom scale (default auto-scales: 0.5x or capped so max dimension ≤ 1024px)
await frame.screenshot({ scale: 2 })
// Include overlapping content from sibling nodes
await frame.screenshot({ contentsOnly: false })
```
**When to use:** Follow Rule 5. Take a composition screenshot when visual evidence is needed. If a visual fix follows, take one post-fix screenshot; that passing screenshot is final. Do not take an additional unchanged “final” screenshot.
**Auto-naming:** The image caption includes node metadata — `"Card (300x150 at 0,60).png"` — giving spatial context without parsing the image.
**Default scaling:** Uses 0.5x scale, but automatically caps so the largest output dimension never exceeds 1024px. Explicit `{ scale: N }` bypasses the cap.
## 6. Incremental Workflow (How to Avoid Bugs)
The most common causes of waste are unnecessary fragmentation, redundant validation, and scripts that cannot be retried safely. **Use Rule 5's safe-retry and evidence-based validation contract.**
### Key rules
- **Choose call boundaries for recoverability, not validation cadence.** Batch related creation, property updates, parenting, and targeted diagnostics when safe. A complete page may be one call; do not split it into header/content/footer calls merely to validate each section. **Slides override:** in Slides files, slides are isolated subtrees — the relevant limit is complexity per slide, not total nodes across slides. Building 3–5 new slides in one call is safe, and so is applying the same edit (e.g. adding a footer, recoloring a heading) across every slide in the deck in a single call. See [figma-use-slides](../figma-use-slides/SKILL.md) for the deck-building workflow.
- **Build top-down, starting with placeholders.** Create the outer structure first with `placeholder = true` on each section, then incrementally replace placeholders with real content in subsequent calls.
### 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. **Build the skeleton.** Create the top-level structure with placeholder sections. Set `placeholder = true` on each section so the user sees progress.
3. **Fill in content in retry-safe batches.** Multiple related sections may be populated together when the operation remains safe to retry. Set each section's `placeholder = false` when done.
4. **Return IDs from every call** (Rule 15) — you'll need created node/variable/collection IDs as inputs to subsequent calls.
5. **Return validation evidence from writes.** Return IDs and the relevant counts, names, hierarchy, or bounds. Add a separate audit only for missing evidence or after a mutation invalidates earlier evidence. Follow Rule 5 for visual checks.
6. **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)
→ return collection, variable, and mode counts
Step 3: Create individual components
→ return component IDs and relevant child/variant counts
Step 4: Compose layouts from component instances
→ return layout IDs/bounds + take a composition screenshot
Step 5: Apply a targeted visual fix only if needed
→ take one post-fix screenshot; this is final
Step 6: Stop if nothing relevant changed
```
### 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 |
## 7. Error Recovery & Self-Correction
On any `use_figma` error, obey `safeToRetryWithoutCanvasRead` (Rule 14): `true` → correct the identified error and retry without adding a diagnostic canvas read; `false` → read the canvas, determine what changed, then make changes. If the same API or property error occurs twice, inspect its definition once and fix the root cause before retrying; do not continue decomposing the operation around the same invalid mutation. Errors whose fix is already a Critical Rule are diagnosed there — `"not implemented"` (`figma.notify`, Rule 3), the `layoutSizing*` HUG/FILL rejections (Rules 12, 12b), `"Setting figma.currentPage is not supported"` (Rule 9), `componentPropertyDefinitions` on a variant (Rule 18), and `characters`/`description` on the wrong node type (Rules 3a, 3b). The rows below cover failures the contract doesn't name:
| Error message | Likely cause | How to fix |
|---|---|---|
| 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
Call `get_metadata` for structural correctness (hierarchy, counts, positions) and `get_screenshot` for visual correctness — look closely for cropped/clipped text (line heights cutting off content) and overlapping elements, which are common and easy to miss. Identify whether the discrepancy is structural or visual, then 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).
## 8. Pre-Flight Checklist
Before submitting ANY `use_figma` call, re-read the script against the operating contract in [Section 1](#1-critical-rules). Every gate that used to be enumerated here now lives there — a script that violates any of these is not ready to submit:
- [ ] **Output** — uses `return` (not `figma.closePlugin()`), not wrapped in an async IIFE, no `console.log()` as output; `return`s structured data with ALL created/mutated node IDs (Rules 1, 2, 4, 15)
- [ ] **Color & fills** — 0–1 range; paint `color` is `{r, g, b}` only (no `a`); fills/strokes reassigned as new arrays (Rules 6, 7)
- [ ] **Type narrowing** — `characters`, `description`, and `componentPropertyDefinitions` guarded/narrowed before access (Rules 3a, 3b, 18)
- [ ] **Pages** — switches use `await figma.setCurrentPageAsync(page)`, at most once per call (Rule 9)
- [ ] **Layout & sizing** — related children in `figma.createAutoLayout()`; top-level nodes positioned away from (0,0); `HUG`/`FILL` set after `appendChild`; `resize()` before sizing modes; wrapping TEXT uses `textAutoResize='HEIGHT'` + FIXED width (Rules 12, 12a, 12b, 12c, 13)
- [ ] **Text** — canonical font recipe with style names verified via `listAvailableFontsAsync()`; `lineHeight`/`letterSpacing` as `{unit, value}`; `FONT_FAMILY` variables load every mode's value first (Rule 8)
- [ ] **Async & state** — every Promise `await`ed; multi-step IDs passed as string literals (Rules 15, 17)
## 9. 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:**
`search_design_system` is an option for published components. For on-canvas components, use the two-step fan-out — **don't loop pages inside one script.**
Step 1: one read-only `use_figma` to get page IDs:
```js
return figma.root.children.map(p => ({ id: p.id, name: p.name }));
```
Step 2: in the **next assistant turn, emit one `use_figma` per page in parallel** (a single message containing N tool-use blocks). Each runs:
```js
const page = await figma.getNodeByIdAsync(PAGE_ID);
await figma.setCurrentPageAsync(page);
// findAllWithCriteria uses an indexed type lookup — hundreds of times faster
// than the findAll(n => n.type === '…') side-effect-in-predicate antipattern.
const matches = page.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] });
return matches.map(n => ({ page: page.name, name: n.name, type: n.type, id: n.id }));
```
**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;
```
## 10. 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 — start with the [canonical text-edit recipe](references/gotchas.md#canonical-text-edit-recipe-font-load--await--mutate--return-ids) |
| [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 discovery via `listAvailableFontsAsync`, 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 |
## 11. 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.
Referenced files: 20
figma-use-figjam6.78 KB
---
name: figma-use-figjam
description: "This skill helps agents use Figma's use_figma MCP tool in the FigJam context. Can be used alongside figma-use which has foundational context for using the use_figma tool."
disable-model-invocation: false
---
# use_figma — Figma Plugin API Skill for FigJam
This skill contains FigJam-specific context for the `use_figma` MCP tool. The [figma-use](../figma-use/SKILL.md) skill provides foundational context for plugin API execution via MCP as well as the full Figma plugin API for more advanced use-cases that are not described here.
**Always include `figma-use-figjam` in the comma-separated `skillNames` parameter when calling `use_figma` for FigJam operations. If this skill was loaded via an MCP resource, you MUST prefix the name with `resource:` (e.g. `resource:figma-use-figjam`).** This is a logging parameter used to track skill usage — it does not affect execution.
> **FigJam URL is `figma.com/board/...`.** Do NOT call `figma.createPage()` in FigJam — it throws `TypeError: figma.createPage no such property 'createPage' on the figma global object`. `createPage()` is a Design-file API only (`figma.com/design/...`). FigJam files have a single implicit page; organize content with sections instead (see [create-section](references/create-section.md)).
## Inspecting FigJam Files
**`get_figjam` is the inspection tool for FigJam files.** It returns the full node tree as XML, including IDs of pages, sections, stickies, connectors, and other nodes you need to reference in subsequent `use_figma` calls.
- **Use `get_figjam` upfront** before writing any `use_figma` code that needs to reference existing nodes (page IDs, section IDs, etc.). Don't try to discover IDs by running an inspection script — `console.log` output from `use_figma` is **not returned to the agent** (see [figma-use Critical Rule #4](../figma-use/SKILL.md)). Only the `return` value comes back.
- **`get_metadata` does NOT work on FigJam files** — it is design-mode only and will fail immediately with "unsupported for FigJam files".
- **`get_screenshot` requires a valid `nodeId`** — passing an empty nodeId returns "invalid nodeId" error. Get IDs from `get_figjam` first.
- If you forgot to `return` an ID from a previous `use_figma` call and need it now, call `get_figjam` rather than re-running an inspection script.
## Loading Reference Docs Efficiently
Load only the references your task needs — but when you do need to load multiple, **issue all reads in a single parallel tool-call batch**, not sequentially across turns. For a typical board-creation task, that means a single message containing reads for `plan-board-content` plus the 3-4 specific node-type references you'll use.
## Deferred Tools — Batch-Load Schemas
The Figma MCP tools (`use_figma`, `get_figjam`, `get_screenshot`, `get_metadata`, `create_new_file`, `whoami`) often appear as deferred tools that require `ToolSearch` to load their schemas before they can be called. **Load all schemas in a single `ToolSearch` call** using the `select:` syntax instead of one call per tool:
```
ToolSearch query="select:use_figma,get_figjam,get_screenshot,get_metadata,create_new_file"
```
Six sequential `ToolSearch` calls is six round trips before any work happens. One batched call is one round trip.
## Text Mutations — Canonical Recipe
Every FigJam text mutation (sticky/shape/label/table cell/connector text, standalone text nodes) follows the same recipe as Design files: load font → `await` → mutate → return affected IDs. Skipping the load throws `Cannot write to node with unloaded font "<family> <style>"`. See [figma-use → gotchas.md → Canonical text-edit recipe](../figma-use/references/gotchas.md#canonical-text-edit-recipe-font-load--await--mutate--return-ids). FigJam-specific note: sublayer defaults vary (sticky → `Inter Medium`, shape → `Inter Medium`, connector → invalid until set), so always load from `node.text.fontName` rather than hardcoding `{ family: 'Inter', style: 'Regular' }`.
## Adding Images to a FigJam Board
**`upload_assets` is the ONLY supported way to add images to a FigJam file.** Call `upload_assets` with the FigJam `fileKey`; the tool returns single-use upload URLs that you POST raw image bytes to, and the image is committed and placed automatically. Pass `nodeIds` (with one entry per upload) to attach uploads to existing FigJam nodes as fills; omit `nodeIds` to drop the images onto the board as new layers.
For the full request/response shape, see [figma-use → api-reference.md → Images](../figma-use/references/api-reference.md#images).
## Reference Docs
- [plan-board-content](references/plan-board-content.md) - Read this for any board content request — board template, retro, brainstorm, ice breaker, meeting board, scaffold
- Covers planning of generated board content, including sequential outline, sections, intents, and hierarchical text
- Delegates to other references for specific API details
- [create-section](references/create-section.md) — Create and configure FigJam sections (sizing, naming, colors, content visibility, organizing nodes, column layouts)
- [create-sticky](references/create-sticky.md) — Create and configure FigJam sticky notes (colors, sizing, text, author visibility, batch creation)
- [create-connector](references/create-connector.md) — Create and configure FigJam connectors (endpoints, arrows, line types, labels, colors, diagram wiring)
- [create-text](references/create-text.md) — Create and configure FigJam text nodes (font loading, preset fonts and colors, sizing, lists, mind map operations)
- [position-figjam-nodes](references/position-figjam-nodes.md) — Position, size, and reparent nodes on the canvas (including within sections)
- [create-shape-with-text](references/create-shape-with-text.md) — Create and configure FigJam shapes with embedded text (shape types, color presets, sizing to fit text, diagram layouts)
- [create-code-block](references/create-code-block.md) — Create and configure FigJam code block nodes (languages, syntax highlighting, positioning, embedding in sections)
- [create-table](references/create-table.md) — Create and configure FigJam tables (rows, columns, cell text, color presets, resizing)
- [edit-text](references/edit-text.md) — Edit existing text nodes (font loading, styled ranges, find/replace, FigJam Charcoal default color)
- [create-label](references/create-label.md) — Create and configure FigJam label nodes (small numbered/lettered circle callout markers, sequences, positioning)
- [batch-modify](references/batch-modify.md) — Patterns for modifying many existing nodes at once (bulk style changes, repositioning, property updates)
- [figjam-colors](references/figjam-colors.md) — Canonical FigJam color palettes for every node type (sticky, section, connector, shape, label) plus the `hex/255` notation rule and the `h()` helper
Referenced files: 13
figma-use-motion6.75 KB
---
name: figma-use-motion
description: "Motion / animation context for the `use_figma` MCP tool — animating Figma nodes via manual keyframes, animation styles, easing, and timeline duration. Load alongside figma-use whenever a task involves adding, editing, or inspecting animation on a node."
disable-model-invocation: false
---
# use_figma — Figma Plugin API Skill for Motion
Motion context for the `use_figma` MCP tool. [figma-use](../figma-use/SKILL.md) covers the foundational Plugin API rules — load both together.
**Always pass `skillNames: "figma-use-motion"` (comma-separated alongside `figma-use`) when calling `use_figma` for motion work.** Logging only.
## Runtime Gating
Motion APIs are gated behind the `metronome` user feature flag. When the calling user doesn't have it, every motion property and helper referenced in this skill throws `"<name>" is not a supported API`.
**Bail out fast on that error.** Do not retry; tell the user motion isn't enabled for them and stop. Otherwise you'll burn calls and confuse the user with repeated identical failures.
## When to use this skill
Load this skill whenever a `use_figma` task involves:
- Adding, editing, or removing keyframes on a node (`manualKeyframeTracks`, `applyManualKeyframeTrack`, `removeManualKeyframeTrack`).
- Animating fill or stroke colors over time.
- Applying, editing, or removing animation styles (`applyAnimationStyle`, `removeAnimationStyle`, `animationStyles`).
- Reading or writing timeline duration via `node.timelines` / `node.setTimelineDuration(id, seconds)`.
- Choosing easing for any of the above.
Static design work (creating shapes, components, variables, layout) goes through [figma-use](../figma-use/SKILL.md) alone — this skill is only for the time dimension.
## Exposed motion API surface
- `node.manualKeyframeTracks` — read/write manual keyframes (including fill, stroke, and effect tracks).
- `node.applyManualKeyframeTrack(field, track)` / `node.removeManualKeyframeTrack(field)` — add, replace, or remove one manual keyframe track without rewriting the whole object.
- `node.animationStyles` — read/write animation-style metadata applied to a node.
- `node.applyAnimationStyle(styleId, presetData?)` / `node.removeAnimationStyle(id)` — apply a discovered style and remove an applied style instance by its returned/read-back `id`.
- `node.timelines` — read-only timeline list for the containing top-level frame, with durations in seconds.
- `node.setTimelineDuration(id, durationSeconds)` — write the containing top-level frame timeline duration.
- `node.animations` — read-only resolved keyframe data (currently manual tracks only — see [motion-patterns.md](references/motion-patterns.md)).
- `figma.motion.figmaAnimationStyles()` — read-only list of Figma's first-party animation styles.
Authoring custom `"figma:motion"` preset module source code is out of scope. If the user wants a brand-new animation style, say so and stop; don't fabricate one.
## Reference docs
Load these as needed based on what the task involves:
| Doc | When to load | What it covers |
|-----|-------------|----------------|
| [motion-patterns.md](references/motion-patterns.md) | Adding/editing motion animation | Manual keyframes, animated fills/strokes, applying animation styles, timeline duration |
| [motion-easing.md](references/motion-easing.md) | Setting animation easing | Keyframe easing objects, custom cubic/spring, `HOLD`, applying easing inside an animation style |
## Verifying the animation
`get_screenshot` shows only the timeline's **resting state**, never motion. To check motion, `export_video` and sample frames — but it renders server-side and is **slow and expensive (~10s to minutes)**, so make each render count.
**Plan before rendering — cost scales with pixels × frames, so keep both no larger than the frames need:**
1. **Pick the moments first.** You need one frame per *phase* (e.g. per stagger step, or start / mid / settle), not smooth playback — usually 4–6. This count sets your fps.
2. **Size to what you must read.** Start small — `constraint: { type: 'WIDTH', value: 320 }`, `quality: "low"` — but text and small elements blur there, so raise `WIDTH` (768+) when you need to judge fine detail. Omitting `constraint` = full size (1x; server clamps to 10x / 4096px).
3. **Set fps just high enough to land those frames:** `fps: 5` covers a handful; 10 is an upper bound. Higher just bloats the render.
**Mechanics:** `export_video` works only on a **top-level frame** whose children carry the animation (pass that frame, not the descendant you keyframed). It returns a `jobId` with `status: "processing"` — re-invoke with `{ fileKey, jobId }` to poll. Then extract frames locally with `ffmpeg -ss <t> -i anim.mp4 -frames:v 1 frame_<t>.png` — extraction is free, so once you've paid for the render, mine it for every frame that tells you something rather than re-exporting. Without a frame extractor like `ffmpeg`, skip the export and reason about the keyframes instead.
**Iterate until it's right.** The export is a diagnostic, not a sign-off: if the frames are wrong (bad order, off timing, a missing element, a mask blanking the composite), fix the keyframes/styles and re-export. Read *all* the frames and batch every fix into one pass before re-rendering — every render carries real overhead, so make each one count instead of re-exporting after each small change.
Skip the export entirely for trivial or self-evident changes.
## Pre-flight checklist
In addition to the [figma-use pre-flight checklist](../figma-use/SKILL.md#8-pre-flight-checklist), verify:
- [ ] Easing uses the public `{ type: 'EASE_OUT', easingFunctionCubicBezier?: …, easingFunctionSpring?: … }` shape — not internal scenegraph names like `OUT_CUBIC`.
- [ ] Ease-in-out uses the exact public enum `EASE_IN_AND_OUT` (or `EASE_IN_AND_OUT_BACK`); never emit the invalid alias `EASE_IN_OUT`.
- [ ] The node being animated is not a top-level frame (direct child of a page). Animate descendants instead.
- [ ] Timeline values are seconds in the public Plugin API. Extend via `setTimelineDuration`; never shorten unless the user asked.
- [ ] Transform keyframe fields use public names (`TRANSLATION_X`, `TRANSLATION_Y`, `ROTATION`, `SCALE_X`, `SCALE_Y`, `SCALE_XY`), not internal `MOTION_*` scenegraph names.
- [ ] Manual keyframe fields come from the public allowlist in [motion-patterns.md](references/motion-patterns.md#animatable-fields); generated/internal scenegraph fields intentionally throw.
- [ ] Mutated node IDs are returned (per `figma-use` Rule 15).
- [ ] When motion correctness isn't self-evident and a frame extractor (`ffmpeg`) is available, verify via `export_video` + frame sampling — render small, low `fps`, iterate until right (see the Verifying the animation section above). `get_screenshot` shows only the resting state.
Referenced files: 2
figma-use-slides21.5 KB
---
name: figma-use-slides
description: "This skill helps agents use Figma's use_figma MCP tool in the Slides context. Can be used alongside figma-use which has foundational context for using the use_figma tool."
disable-model-invocation: false
---
# use_figma — Figma Plugin API Skill for Slides
This skill contains Slides-specific context for the `use_figma` MCP tool. The [figma-use](../figma-use/SKILL.md) skill provides foundational context for plugin API execution via MCP as well as the full Figma plugin API for more advanced use-cases that are not described here.
**Always include `figma-use-slides` in the comma-separated `skillNames` parameter when calling `use_figma` for Slides operations. If this skill was loaded via an MCP resource, you MUST prefix the name with `resource:` (e.g. `resource:figma-use-slides`).** This is a logging parameter used to track skill usage — it does not affect execution.
## Choosing How to Build a Slides Deck
If your environment also provides a `generate_deck` tool, choose **one** approach per deck request — do not call both for the same deck.
### When to use which
**`use_figma` + this skill (default):** Handles any Slides task — new decks, edits to existing decks, brand-matched designs, reference-file styling, iterative refinement, speaker notes, and full creative control over layout, color, and typography. Use this for most requests.
**`generate_deck`:** Generates a complete deck in a single call using prebuilt, curated templates. Useful for quick, straightforward decks where the user doesn't need custom design, brand matching, or reference-file styling. It cannot use custom templates, cannot reference other Figma files for design direction, and does not support iterative editing or follow-up modifications through the conversation.
When in doubt, default to `use_figma` + this skill — it covers everything `generate_deck` can do and more.
### Pick one and commit
Once you choose an approach for a deck, complete the entire request with that approach. Do not generate a deck with `generate_deck` and then also create or populate a file with `use_figma` — the user ends up with duplicate, conflicting artifacts and a confusing experience.
## Critical Rules (Slides-specific)
1. **Newly created Slides files have a default light theme.** When a Slides file is created via `create_new_file`, a default light theme is automatically initialized. This theme is structural scaffolding — you should overwrite the theme's color variables and text styles with your own design direction for the deck you're building. Do not rely on or be influenced by the default light theme tokens.
2. **MUST `appendChild` BEFORE setting `x`/`y` — for every node, at every level of nesting.** Newly created nodes are silently auto-parented to a slide context at absolute `(240, 240)` (the slide grid's `GRID_PADDING`). Writing `x`/`y` before `appendChild` causes the value to be stored against that hidden origin; the node then lands at `(intended − 240, intended − 240)` once you attach the real parent. The bug is **intermittent** — some frames in the same script escape it, so a working test is not proof you're safe. **Signature to recognize:** if any node ends up `(−240, −240)` from where you set it, your code set `x`/`y` before the final `appendChild`. Do NOT try to compensate by adding 240 back — that produces worse output on retry. Fix the order instead. See [slide-gotchas.md](references/slide-gotchas.md#position-after-appendchild-critical) for the helper pattern that makes the order impossible to get wrong.
3. **SLIDE_GRID and SLIDE_ROW are opaque nodes** — do not access `.fills`, `.effects`, or layout properties on them. Only `SLIDE` nodes (type `'SLIDE'`) extend `BaseFrameMixin`. **Exception:** `SLIDE_ROW.name` IS settable — that's how plugins rename slide sections (e.g. `slideRow.name = "Intro"`). See [slide-lifecycle.md](references/slide-lifecycle.md).
4. **`get_metadata` does NOT work on Slides files.** Use `use_figma` read-only scripts for validation. Return created node positions in `closePlugin()` output and verify no overlapping bounding boxes.
5. **Do NOT call `figma.createPage()` in Slides.** It throws `TypeError: figma.createPage no such property 'createPage' on the figma global object` — `createPage()` is a Design-file API only (`figma.com/design/...`); the Slides URL is `figma.com/slides/...`. Use the slide grid (`SLIDE_GRID` / `SLIDE_ROW` / `SLIDE`) to organize deck structure instead — see [slide-lifecycle.md](references/slide-lifecycle.md) and [slide-grid.md](references/slide-grid.md).
6. **Never delete existing slides to rebuild them.** When asked to improve, redesign, or restyle a deck, modify the existing slides in place. Only delete slides when the user explicitly asks to "start over" or "redo from scratch."
## Design Thinking
Not every task needs the same depth of design thinking. Before doing anything, identify which gear you're in:
- **Content/property edits** — changing text, swapping a color, updating a number, fixing alignment, resizing an element. Skip design thinking. Just make the change and match what's already there.
- **Structural additions** — adding slides, reworking a section's layout, changing the deck's color palette, introducing a new visual element. This includes requests to "improve," "redesign," or "restyle" a deck — those are in-place edits to what's already there, not a new deck. Design thinking applies, but in *inherit* mode: the existing deck is your design language. Inspect it, match its palette, type, spatial habits, and motifs. Extend the deck's existing character rather than reinventing it.
- **New deck creation** — building a deck from scratch or from a blank file. Full design thinking applies as described below.
For structural additions to existing decks: run the inspection scripts (below) and take screenshots before making changes. The answers to "what color story?" and "what type treatment?" are already in the file — your job is to read them and stay consistent. The design principles in [slide-design.md](references/slide-design.md) describe what you're *matching*, not what you're *choosing*.
### New deck design process
Before writing any Plugin API code for a new deck, decide what it should *feel* like. Figma users have high visual expectations — a deck that looks like it came out of a generic template generator will stand out for the wrong reasons.
1. **Read the brief.** What is the deck communicating, and to whom? An investor pitch, a team retrospective, a product launch, and a technical deep-dive all demand different visual treatments. The design should be inseparable from the content.
2. **Check for a design language.** Before inventing anything, look at what the user already gave you. Brand guidelines in the prompt — color palettes, typography specs, logo rules, tone descriptors — are design decisions that have already been made. A link to a reference Figma file is a design language you should study, not glance at. The more specific the user's inputs, the less you should invent on your own. When the user provides a reference, your job shifts from *designer* to *interpreter*: extract the design language and apply it faithfully to new content.
3. **Take a position — on what's left.** If the user supplied a full brand system, your creative latitude is in layout, pacing, and composition — not in color or type. If they gave you a single reference slide for inspiration, you have more room but should still echo its character. If they gave you nothing, then you own every decision — choose a color story, a type treatment, a way of organizing space, and follow through on it across every slide. A deck with a clear perspective (even a quiet one) always reads better than one that plays it safe on every decision. The scope of "take a position" scales inversely with what the user provided.
4. **Give it a signature.** Every good deck has at least one element you'd recognize if you saw it out of context: a distinctive palette, an unexpected layout cadence, a recurring shape language. When working from brand guidelines, the signature should *come from* that brand language — amplify something that's already there rather than adding something foreign. When designing from scratch, decide what the signature is before you start building.
### Reading a reference file
When the user provides a link to a Figma file as a reference, study it before designing anything. What you extract depends on what the file is:
- **A Slides file**: `get_metadata` does not work on Slides files. Use `get_screenshot` to capture individual slides for visual reference, and `use_figma` with the reference file's `fileKey` to run read-only scripts that extract theme variables, color palettes, font choices, and layout patterns.
- **A Design file**: `get_design_context` gives you comprehensive design data — colors, typography, layout structure. `get_screenshot` gives you visual reference. Use both.
What to look for in a reference file: the color palette (which hue leads, what the accent is, how dark/light backgrounds are used), the type choices (families, weights, how hierarchy is handled), the spatial habits (where content anchors, how much whitespace, whether things bleed off edges), and any recurring motifs (shapes, line treatments, decorative elements). These are the decisions you inherit — everything else is yours.
How closely to follow the reference depends on what the user asked for. "Make it look like this" means replicate the design language with new content. "Use this for inspiration" means echo the character but make it your own. "Here's our brand deck" means extract the brand system and apply it consistently. When in doubt, stay closer to the reference — it's easier for a user to ask you to diverge than to ask you to undo invented choices that conflict with their brand.
Load [slide-design.md](references/slide-design.md) for specific guidance on color, type, layout patterns, composition, and what to avoid. When you have a reference file or brand guidelines, treat slide-design.md's principles as defaults for the decisions the user *didn't* make — not as overrides for the ones they did.
## Deck-Building Workflow
When building a new deck of 5 or more slides, use this two-phase workflow. It replaces the general incremental workflow from [figma-use](../figma-use/SKILL.md) Section 6 for deck-building specifically — the principles still apply, but the cadence changes.
### Phase 1 — Design & Plan
Complete the design thinking process above (read the brief, check for a design language, take a position, give it a signature), then **before writing any `use_figma` code**, produce a slide plan covering the entire deck:
1. **Slide-by-slide plan.** For every slide: its purpose/content, layout approach described spatially (e.g. "title anchored upper-left, spec card filling the right third, decorative circle bleeding off top-right edge"), and background treatment (dark/light/gradient). Do NOT compute pixel coordinates during planning — describe layouts in spatial terms. Coordinate math happens during code generation.
2. **Shared constants.** Declare the font families and styles you'll use, the color palette as named roles (primary, accent, bgDark, surface, textPrimary, textMuted, etc.), and the recurring motif or signature element.
3. **Layout variety check.** Read through the slide plans in sequence. If the layout descriptions feel repetitive — "two-column, two-column, grid, two-column" — rearrange before building. This is the cheapest moment to diversify. See [slide-design.md](references/slide-design.md) for anti-patterns.
4. **Code preamble.** Write out the reusable preamble you'll paste at the top of every build script: a `const C = { ... }` color palette object, a `Promise.all([...])` font-loading block, and the `addFrame`/`addText`/`addRect` helpers from [slide-gotchas.md](references/slide-gotchas.md#position-after-appendchild-critical).
### Phase 2 — Build
Execute the plan in large batches. The goal is to minimize the number of think-then-build cycles — not to minimize elements per script.
- **3–5 slides per `use_figma` call.** Structurally similar slides (e.g. a series of product feature slides) can go in the same batch. Each slide is an isolated subtree — cross-slide dependencies don't exist, so large batches are safe.
- **Do NOT re-plan between batches.** The design was decided in Phase 1. If a batch succeeds and passes validation, move to the next batch immediately. Only re-plan if a batch fails or produces a visual problem that requires changing the approach.
- **Paste the code preamble** (colors, fonts, helpers) at the top of every build script. Copy it from Phase 1 verbatim — do not re-derive it.
- **Validate every batch** with the deterministic batch validation script from [slide-gotchas.md](references/slide-gotchas.md#batch-validation-script). This checks for overlapping elements, text clipping, and out-of-bounds nodes in ~3 seconds. If the check passes, proceed without a screenshot. If it fails, screenshot the affected slides and fix before continuing.
- **Screenshot at checkpoints only** — after the first batch (validates the visual system: colors, typography, design direction), and after the final batch (overall quality). Take a screenshot of 1–2 representative slides per checkpoint using inline `await slide.screenshot()`, not separate `get_screenshot` calls.
- **Return all created node IDs** from every build script, as always.
## Sections
A section is a horizontal row in the slide grid — every row is a section. Names show up in the editor (next to the row) and in Presenter View (so speakers can jump between groups). They're an organizational aid for whoever is editing the deck — the user owns where the breaks fall, not you.
### When asked to organize a deck
"Organize this deck" is ambiguous — grouping, reordering, deduping, or restructuring. Read the deck before reaching for `AskUserQuestion`.
**Default: propose, don't ask.** Most decks have cues — title bookend, numbered use cases, repeated *Before / After* pairs, transition slides ("Then X enters the chat"), a *Thank you*. When cues exist, pick a sectioning and surface it in one confirmation message. Bounded calls inside the proposal (one *Use Cases* row vs. three, where a transition slide lives) are reversible — pick one and move on.
**Fallback: ask when cues are absent.** If slides are in arbitrary order or there's no spine, ask which ranges go together and what to call them. Don't slice by thirds as a substitute for reading.
### Naming + scoping
Names should be short (1–3 words), concrete (*Demo* beats *Show & tell*), and consistent within a deck. Two to five sections is typical; more only for long or repeating decks. Names aren't slide titles — they help find a group, not describe its content.
### Renaming a section
`getSlideGrid()` returns `SlideNode[][]` — the inner arrays are plain JS arrays of slides, NOT `SLIDE_ROW` nodes. Setting `.name` on those arrays silently no-ops. To rename a section, traverse the node tree and set `.name` on the actual `SLIDE_ROW`:
```js
const slideGrid = figma.currentPage.children.find(c => c.type === "SLIDE_GRID");
slideGrid.children[0].name = "Intro";
```
## Speaker Notes
Speaker notes are the presenter's private companion to each slide. They appear in Presenter View (visible only to the speaker, not the audience) and serve as a script, cue sheet, or talking-points reference during a live presentation.
### When to write speaker notes
- **When asked**: If the user asks for speaker notes, presenter notes, talking points, or a script for a deck, write notes for every slide that has substantive content (skip section dividers or purely decorative slides unless there's something to say).
- **Presenter-ready decks**: If the user explicitly asks for a deck that is ready to present live, speaker notes are useful. Add them when they help the presenter understand pacing, transitions, or context that is not visible on the slide.
- **Sparse or visual slides**: If a slide is built around a chart, image, metaphor, or provocative question, notes can help explain what the presenter should say. Use screenshots or `node.screenshot()` for image-heavy, chart-heavy, or visually sparse slides when visual context matters, but don't screenshot every slide by default — images spend context budget.
- **Don't add notes unprompted**: For normal slide edits, layout work, or updates to existing decks, do not populate speaker notes unless the user asks. Adding notes changes the presentation flow and can surprise the deck owner.
### What good speaker notes look like
Speaker notes are for the *presenter*, not the audience. They should feel like a trusted colleague leaning over and whispering "here's what to say." Good notes:
- **Complement the slide, not repeat it.** If the slide says "Revenue grew 40%", the notes shouldn't say "Revenue grew 40%." They should say *why* it grew, what the audience should take away, or what question this usually prompts.
- **Are concise and scannable.** A presenter glancing down mid-sentence needs to find their place instantly. Use short bullet points, not dense paragraphs. Each point should be one idea.
- **Include transitions.** The best notes tell the presenter how to *move* between slides: "After the applause dies down..." or "This builds on the previous point — call back to the 40% figure."
- **Carry context the slide can't.** Data sources ("Source: Q4 FY25 internal metrics, not yet public"), caveats ("Skip this slide if the CFO is in the room"), timing cues ("This is the halfway point — you should be at ~10 minutes"), and anticipated questions ("They'll ask about margins — see appendix slide 14").
- **Match the presentation's register.** Notes for an investor pitch are precise and rehearsed. Notes for a team retro are casual and flexible. Notes for a keynote might include stage directions. Match the tone to the context.
### What to avoid in speaker notes
- **Full scripts**: Wall-of-text notes encourage reading verbatim, which makes for a terrible presentation. If the user explicitly asks for a script, write one, but default to bullet points.
- **Formatting for the audience**: Notes aren't visible to the audience. Don't optimize them for readability by non-presenters.
- **Redundancy with the slide**: If the slide is self-explanatory ("Thank You" with contact info), notes aren't needed. It's fine to leave a slide's notes empty.
### Formatting
`slide.speakerNotes` accepts a markdown string. Prefer bullet lists as the primary structure; bold is useful for emphasis on key phrases the presenter shouldn't skip. See [slide-properties.md](references/slide-properties.md#supported-formatting) for the full list of supported (lists, bold, italic, strikethrough) and unsupported (headings, code blocks, inline code, links) markdown.
## Inspecting Slides Files
There is no dedicated read tool for Slides files yet. Use `use_figma` with read-only scripts for inspection, and `get_screenshot` / `await node.screenshot()` for visual context.
- **Inspect before creating.** Before creating anything, run a read-only `use_figma` to discover what already exists — slides, text, components, naming conventions. The [figma-use](../figma-use/SKILL.md) Section 6 "Inspect first" pattern applies here.
- **`get_metadata` does NOT work on Slides files** — it only supports `figma` (Design) editor type.
- **`console.log()` output is NOT returned** — only the `return` value comes back. Always `return` the data you need.
- **Use `get_screenshot` for visual context** — pass a valid `nodeId` to get a screenshot. You can also use `await node.screenshot()` inline within `use_figma` scripts.
### Quick inspection scripts
**List all slides in the deck:**
```js
const grid = figma.getSlideGrid();
return grid.map((row, rowIdx) =>
row.map((slide, colIdx) => ({
id: slide.id,
name: slide.name,
row: rowIdx,
col: colIdx,
isSkipped: slide.isSkippedSlide,
speakerNotes: slide.speakerNotes,
}))
);
```
**Get text content from a specific slide:**
```js
const slide = figma.getNodeById("TARGET_SLIDE_ID");
// findAllWithCriteria uses an indexed type lookup — much faster than
// findAll(n => n.type === 'TEXT') on slides with many shapes/images.
const textNodes = slide.findAllWithCriteria({ types: ["TEXT"] });
const fontsToLoad = new Set();
for (const t of textNodes) {
if (t.fontName !== figma.mixed) {
fontsToLoad.add(JSON.stringify(t.fontName));
} else {
const segments = t.getStyledTextSegments(["fontName"]);
for (const seg of segments) fontsToLoad.add(JSON.stringify(seg.fontName));
}
}
for (const f of fontsToLoad) {
await figma.loadFontAsync(JSON.parse(f));
}
return textNodes.map(t => ({
id: t.id,
name: t.name,
characters: t.characters,
x: t.x,
y: t.y,
width: t.width,
height: t.height,
}));
```
## Reference Docs
Load only the references your task needs:
- [slide-gotchas](references/slide-gotchas.md) — Pitfalls specific to Slides (coordinate offsets, opaque node types, validation workarounds)
- [slide-lifecycle](references/slide-lifecycle.md) — Create, clone, delete, and reorder slides and slide rows
- [slide-grid](references/slide-grid.md) — Work with the slide grid layout (`getSlideGrid`, `setSlideGrid`)
- [slide-content](references/slide-content.md) — Build content within slides (text, shapes, auto-layout — SlideNode extends BaseFrameMixin)
- [slide-properties](references/slide-properties.md) — Slide-specific properties (`speakerNotes`, `isSkippedSlide`, `focusedSlide`, `focusedNode`, `slideThemeId`, `InteractiveSlideElementNode`)
- [slide-design](references/slide-design.md) — Design principles for visually interesting, varied decks (color strategy, typography, layout variety, spatial composition, anti-patterns)
Referenced files: 6
Technical details
- First seen
- Sep 30, 2026 · 22:02 UTC
- Last seen
- Oct 1, 2026 · 12:00 UTC
- Collection status
- Collected
plugin_connector_68df038e0ba48191908c8434991bbac2
Download listing JSON