Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
@@ -0,0 +1,304 @@
# Figma Plugin API Reference
> Part of the [use_figma skill](../SKILL.md). What works and what doesn't in the `use_figma` environment.
## Contents
- Node Creation
- Grouping and Boolean Operations
- Library Imports
- Variables API
- Core Properties
- Node Manipulation
- Descriptions and Documentation Links
- SVG and Images
- Utilities and Plugin Lifecycle
- Node Traversal
- Unsupported APIs
## Node Creation (Design Mode)
```js
figma.createRectangle()
figma.createFrame()
figma.createComponent() // Creates a ComponentNode
figma.createText()
figma.createEllipse()
figma.createStar()
figma.createLine()
figma.createVector()
figma.createPolygon()
figma.createBooleanOperation()
figma.createSlice()
figma.createPage() // Page node can be created, but child persistence is limited in headless mode
figma.createSection()
figma.createTextPath()
```
## Grouping & Boolean Operations
```js
figma.group(nodes, parent, index?) // Group nodes
figma.flatten(nodes, parent?, index?) // Flatten to vector
figma.union(nodes, parent?, index?) // Boolean union
figma.subtract(nodes, parent?, index?) // Boolean subtract
figma.intersect(nodes, parent?, index?) // Boolean intersect
figma.exclude(nodes, parent?, index?) // Boolean exclude
figma.combineAsVariants(components, parent?) // Combine ComponentNodes into ComponentSet (Design/Sites only)
```
## Library Component Import
These methods import components from **team libraries** (not the same file you're working in). For components in the current file, use `use_figma` with `figma.getNodeByIdAsync()` or `findOne()`/`findAll()` to locate them directly.
```js
// Import a published component from a team library by key
const comp = await figma.importComponentByKeyAsync("COMPONENT_KEY")
const instance = comp.createInstance()
// Import a published component set from a team library by key
const compSet = await figma.importComponentSetByKeyAsync("COMPONENT_SET_KEY")
const variant =
compSet.children.find((c) => c.type === "COMPONENT" && c.name.includes("size=md")) ||
compSet.defaultVariant
const variantInstance = variant.createInstance()
```
## Library Style Import (Team Libraries)
These methods import styles from **team libraries** (not the same file). For styles in the current file, use `figma.getLocalPaintStyles()`, `figma.getLocalTextStyles()`, etc.
```js
// Import a published style from a team library by key
const style = await figma.importStyleByKeyAsync("STYLE_KEY")
// Apply the imported style to a node
await node.setFillStyleIdAsync(style.id) // for PaintStyle as fill
await node.setStrokeStyleIdAsync(style.id) // for PaintStyle as stroke
await node.setTextStyleIdAsync(style.id) // for TextStyle
await node.setEffectStyleIdAsync(style.id) // for EffectStyle
await node.setGridStyleIdAsync(style.id) // for GridStyle
```
## Library Variable Import (Team Libraries)
This imports variables from **team libraries** (not the same file). For variables in the current file, use `figma.variables.getLocalVariablesAsync()` or `figma.variables.getVariableByIdAsync()`.
```js
// Import a published variable from a team library by key
const variable = await figma.variables.importVariableByKeyAsync("VARIABLE_KEY")
// Bind the imported variable to node properties
node.setBoundVariable("width", variable) // FLOAT variable
// Bind to fills/strokes (COLOR variable) — returns a NEW paint, must capture it
const newPaint = figma.variables.setBoundVariableForPaint(paintCopy, "color", variable)
node.fills = [newPaint]
```
## Variables API
```js
// Collections
const collection = figma.variables.createVariableCollection("Name")
collection.name // Get/set name
collection.modes // Array of {modeId, name} — starts with 1 mode
collection.addMode("Dark") // Returns new modeId string
collection.renameMode(modeId, "Light")
// Variables
const variable = figma.variables.createVariable("name", collection, "COLOR")
// ^ must be a collection object (passing an ID string is deprecated)
// resolvedType: "COLOR" | "FLOAT" | "STRING" | "BOOLEAN"
variable.setValueForMode(modeId, value)
// Scopes — controls where variable appears in property pickers
variable.scopes = ["FRAME_FILL", "SHAPE_FILL"] // only fill pickers
variable.scopes = ["TEXT_FILL"] // only text color picker
variable.scopes = ["STROKE_COLOR"] // only stroke picker
variable.scopes = [] // hidden from all pickers (use for primitives)
// All valid scope values:
// ALL_SCOPES, TEXT_CONTENT, CORNER_RADIUS, WIDTH_HEIGHT, GAP,
// ALL_FILLS, FRAME_FILL, SHAPE_FILL, TEXT_FILL,
// STROKE_COLOR, STROKE_FLOAT, EFFECT_FLOAT, EFFECT_COLOR,
// OPACITY, FONT_FAMILY, FONT_STYLE, FONT_WEIGHT, FONT_SIZE,
// LINE_HEIGHT, LETTER_SPACING, PARAGRAPH_SPACING, PARAGRAPH_INDENT
// Querying (always use the Async variants — sync versions are deprecated)
await figma.variables.getVariableByIdAsync(id)
await figma.variables.getLocalVariablesAsync(resolvedType?)
await figma.variables.getVariableCollectionByIdAsync(id)
await figma.variables.getLocalVariableCollectionsAsync()
// Binding variables to paints (COLOR variables)
const newPaint = figma.variables.setBoundVariableForPaint(paintCopy, "color", variable)
// ⚠️ Returns a NEW paint — must capture return value!
node.fills = [newPaint]
// Binding variables to effects (COLOR/FLOAT variables)
const newEffect = figma.variables.setBoundVariableForEffect(effectCopy, field, variable)
// field for shadows: "color" (COLOR), "radius" | "spread" | "offsetX" | "offsetY" (FLOAT)
// field for blurs: "radius" (FLOAT)
// ⚠️ Returns a NEW effect — must capture return value!
node.effects = [newEffect]
// Binding variables to layout grids (FLOAT variables)
const newGrid = figma.variables.setBoundVariableForLayoutGrid(gridCopy, field, variable)
// field: "sectionSize" | "offset" | "count" | "gutterSize"
// ⚠️ Returns a NEW layout grid — must capture return value!
node.layoutGrids = [newGrid]
// Binding variables to node properties (FLOAT/STRING/BOOLEAN)
// Layout & sizing (FLOAT):
node.setBoundVariable("width", variable)
node.setBoundVariable("height", variable)
node.setBoundVariable("minWidth", variable)
node.setBoundVariable("maxWidth", variable)
node.setBoundVariable("minHeight", variable)
node.setBoundVariable("maxHeight", variable)
node.setBoundVariable("paddingLeft", variable)
node.setBoundVariable("paddingRight", variable)
node.setBoundVariable("paddingTop", variable)
node.setBoundVariable("paddingBottom", variable)
node.setBoundVariable("itemSpacing", variable)
node.setBoundVariable("counterAxisSpacing", variable)
// Corner radii (FLOAT) — use individual corners, NOT cornerRadius:
node.setBoundVariable("topLeftRadius", variable)
node.setBoundVariable("topRightRadius", variable)
node.setBoundVariable("bottomLeftRadius", variable)
node.setBoundVariable("bottomRightRadius", variable)
// Other (FLOAT):
node.setBoundVariable("opacity", variable)
node.setBoundVariable("strokeWeight", variable)
// ⚠️ fontSize, fontWeight, lineHeight are NOT bindable via setBoundVariable
// — set these directly as values on text nodes
// Aliases
figma.variables.createVariableAlias(variable)
// Explicit modes — CRITICAL for variant components
node.setExplicitVariableModeForCollection(collection, modeId) // pass collection object, NOT an ID string
// Without this, all nodes use the default (first) mode of the collection
```
## Core Properties
```js
figma.root // DocumentNode
figma.currentPage // Current page (read-only in use_figma; sync setter throws)
figma.setCurrentPageAsync(page) // Switch page and load its content (MUST await)
figma.fileKey // File key string
figma.mixed // Mixed sentinel value
```
## Node Manipulation
```js
// Fills & Strokes (read-only arrays — must clone)
node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
node.strokes = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }]
node.strokeWeight = 1
node.strokeAlign = 'INSIDE' // 'INSIDE' | 'CENTER' | 'OUTSIDE'
// Effects
node.effects = [{ type: 'DROP_SHADOW', color: {r:0,g:0,b:0,a:0.25}, offset:{x:0,y:4}, radius:4, visible:true }]
// Layout
node.layoutMode = 'HORIZONTAL' // 'NONE' | 'HORIZONTAL' | 'VERTICAL'
node.primaryAxisAlignItems = 'CENTER' // 'MIN' | 'CENTER' | 'MAX' | 'SPACE_BETWEEN'
node.counterAxisAlignItems = 'CENTER' // 'MIN' | 'CENTER' | 'MAX' | 'BASELINE'
node.paddingLeft = 8
node.paddingRight = 8
node.paddingTop = 4
node.paddingBottom = 4
node.itemSpacing = 4
node.layoutSizingHorizontal = 'HUG' // 'FIXED' | 'HUG' | 'FILL'
node.layoutSizingVertical = 'HUG' // 'FIXED' | 'HUG' | 'FILL'
// Sizing
node.resize(width, height) // ⚠️ Resets sizing modes to FIXED
node.resizeWithoutConstraints(width, height) // Doesn't affect constraints
// Corner radius
node.cornerRadius = 8
// Visibility & Opacity
node.visible = true
node.opacity = 0.5
// Naming & Hierarchy
node.name = "My Node"
parent.appendChild(child)
parent.insertChild(index, child)
node.remove()
```
## Descriptions & Documentation Links
```js
// Description — plain text, shown in Figma's component panel
node.description = "A short summary of this component's purpose and usage."
// Documentation links — array of {uri, label} shown as clickable links
componentSet.documentationLinks = [
{ uri: "https://example.com/docs", label: "Component Docs" }
]
// ⚠️ uri MUST be a valid URL (https://...) — relative paths will throw
```
## SVG Import
```js
const svgNode = figma.createNodeFromSvg('<svg>...</svg>')
```
## Images
```js
const image = figma.createImage(uint8Array)
node.fills = [{ type: 'IMAGE', scaleMode: 'FILL', imageHash: image.hash }]
```
## Utilities
```js
figma.base64Encode(uint8Array) // Uint8Array → base64 string
figma.base64Decode(base64String) // base64 string → Uint8Array
figma.createComponentFromNode(node) // Convert existing node to component (Design/Sites only)
```
## Plugin Lifecycle
Scripts are automatically wrapped in an async IIFE with error handling. Use `return` to send data back:
```js
return { nodeId: frame.id } // Return object — auto-serialized to JSON
return "success message" // Return string
// Errors are auto-captured — no try/catch or closePlugin needed
```
## Node Traversal
```js
node.findAll(pred?) // Find all descendants matching predicate
node.findOne(pred?) // Find first descendant matching predicate
node.findChildren(pred?) // Find direct children matching predicate
node.findChild(pred?) // Find first direct child matching predicate
node.children // Direct children array
node.parent // Parent node
```
---
## What Does NOT Work
| API | Status |
|-----|--------|
| `figma.notify()` | **Throws "not implemented"** — most common mistake |
| `figma.showUI()` | No-op (silently ignored) |
| `figma.openExternal()` | No-op (silently ignored) |
| `figma.listAvailableFontsAsync()` | Not implemented |
| `figma.loadAllPagesAsync()` | Not implemented |
| `figma.variables.extendLibraryCollectionByKeyAsync()` | Not implemented |
| `figma.teamLibrary.*` | Not implemented (requires LiveGraph) |
@@ -0,0 +1,441 @@
# Common Patterns
> Part of the [use_figma skill](../SKILL.md). Working code examples for frequently used operations.
## Contents
- Basic Script Structure
- Create a Styled Shape
- Create a Text Node
- Create Frame with Auto-Layout
- Create Variable Collections and Bindings
- Create Components and Import by Key
- Component Sets with Variable Modes
- Multi-Step Large ComponentSet Pattern
- Read Existing Nodes and Return Data
## Basic Script Structure
```js
const createdNodeIds = []
const mutatedNodeIds = []
// Your code here — track every node you create or mutate
// createdNodeIds.push(newNode.id)
// mutatedNodeIds.push(existingNode.id)
return {
success: true,
createdNodeIds,
mutatedNodeIds,
// Plus any other useful data for subsequent calls
count: createdNodeIds.length
}
```
## Create a Styled Shape
```js
// Find clear space to the right of existing content
const page = figma.currentPage
let maxX = 0
for (const child of page.children) {
maxX = Math.max(maxX, child.x + child.width)
}
const rect = figma.createRectangle()
rect.name = "Blue Box"
rect.resize(200, 100)
rect.fills = [{ type: 'SOLID', color: { r: 0.047, g: 0.549, b: 0.914 } }]
rect.cornerRadius = 8
rect.x = maxX + 100 // offset from existing content
rect.y = 0
figma.currentPage.appendChild(rect)
return { nodeId: rect.id }
```
## Create a Text Node
```js
// Find clear space to the right of existing content
const page = figma.currentPage
let maxX = 0
for (const child of page.children) {
maxX = Math.max(maxX, child.x + child.width)
}
await figma.loadFontAsync({ family: "Inter", style: "Regular" })
const text = figma.createText()
text.characters = "Hello World"
text.fontSize = 16
text.fills = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }]
text.textAutoResize = 'WIDTH_AND_HEIGHT'
text.x = maxX + 100
text.y = 0
figma.currentPage.appendChild(text)
return { nodeId: text.id }
```
## Create Frame with Auto-Layout
```js
// Find clear space to the right of existing content
const page = figma.currentPage
let maxX = 0
for (const child of page.children) {
maxX = Math.max(maxX, child.x + child.width)
}
const frame = figma.createFrame()
frame.name = "Card"
frame.layoutMode = 'VERTICAL'
frame.primaryAxisAlignItems = 'MIN'
frame.counterAxisAlignItems = 'MIN'
frame.paddingLeft = 16
frame.paddingRight = 16
frame.paddingTop = 12
frame.paddingBottom = 12
frame.itemSpacing = 8
frame.layoutSizingHorizontal = 'HUG'
frame.layoutSizingVertical = 'HUG'
frame.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
frame.cornerRadius = 8
frame.x = maxX + 100
frame.y = 0
figma.currentPage.appendChild(frame)
return { nodeId: frame.id }
```
## Create Variable Collection with Multiple Modes
```js
const collection = figma.variables.createVariableCollection("Theme/Colors")
// Rename the default mode
collection.renameMode(collection.modes[0].modeId, "Light")
const darkModeId = collection.addMode("Dark")
const lightModeId = collection.modes[0].modeId
const bgVar = figma.variables.createVariable("bg", collection, "COLOR")
bgVar.setValueForMode(lightModeId, { r: 1, g: 1, b: 1, a: 1 })
bgVar.setValueForMode(darkModeId, { r: 0.1, g: 0.1, b: 0.1, a: 1 })
const textVar = figma.variables.createVariable("text", collection, "COLOR")
textVar.setValueForMode(lightModeId, { r: 0, g: 0, b: 0, a: 1 })
textVar.setValueForMode(darkModeId, { r: 1, g: 1, b: 1, a: 1 })
return {
collectionId: collection.id,
lightModeId,
darkModeId,
bgVarId: bgVar.id,
textVarId: textVar.id
}
```
## Bind Color Variable to a Fill
```js
const variable = await figma.variables.getVariableByIdAsync("VariableID:1:2")
const rect = figma.createRectangle()
const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }
// setBoundVariableForPaint returns a NEW paint — capture it!
const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", variable)
rect.fills = [boundPaint]
return { nodeId: rect.id }
```
## Create Component Variants with Component Properties
Component properties (TEXT, BOOLEAN, INSTANCE_SWAP) MUST be added inside the per-variant loop, BEFORE `combineAsVariants`. The component set inherits them from its children.
```js
await figma.loadFontAsync({ family: "Inter", style: "Regular" })
// Assume defaultIconComp is an existing icon component (discovered earlier)
const defaultIconComp = figma.getNodeById('ICON_COMPONENT_ID')
const components = []
const variants = ["primary", "secondary"]
for (const variant of variants) {
const comp = figma.createComponent()
comp.name = `variant=${variant}`
comp.layoutMode = 'HORIZONTAL'
comp.primaryAxisAlignItems = 'CENTER'
comp.counterAxisAlignItems = 'CENTER'
comp.paddingLeft = 12
comp.paddingRight = 12
comp.paddingTop = 8
comp.paddingBottom = 8
comp.layoutSizingHorizontal = 'HUG'
comp.layoutSizingVertical = 'HUG'
comp.cornerRadius = 6
comp.itemSpacing = 8
// TEXT property — label
const labelKey = comp.addComponentProperty('Label', 'TEXT', 'Button')
const label = figma.createText()
label.characters = "Button"
label.fontSize = 14
comp.appendChild(label)
label.componentPropertyReferences = { characters: labelKey }
// BOOLEAN + INSTANCE_SWAP — icon slot
const showIconKey = comp.addComponentProperty('Show Icon', 'BOOLEAN', false)
const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', defaultIconComp.id)
const iconInstance = defaultIconComp.createInstance()
comp.insertChild(0, iconInstance) // icon before label
iconInstance.componentPropertyReferences = {
visible: showIconKey,
mainComponent: iconSlotKey
}
components.push(comp)
}
const componentSet = figma.combineAsVariants(components, figma.currentPage)
componentSet.name = "Button"
// Layout variants in a row after combining (they stack at 0,0 by default)
const colW = 140
componentSet.children.forEach((child, i) => {
child.x = i * colW
child.y = 0
})
// Resize from actual child bounds — formula-based sizing is error-prone
let maxX = 0, maxY = 0
for (const c of componentSet.children) {
maxX = Math.max(maxX, c.x + c.width)
maxY = Math.max(maxY, c.y + c.height)
}
componentSet.resizeWithoutConstraints(maxX + 40, maxY + 40)
return {
componentSetId: componentSet.id,
componentIds: components.map(c => c.id)
}
```
## Import a Component by Key (Team Libraries)
`importComponentByKeyAsync` and `importComponentSetByKeyAsync` import components from **team libraries** (not the same file you're working in). For components in the current file, use `figma.getNodeByIdAsync()` or `findOne()`/`findAll()` to locate them directly.
```js
// Import a single published component by key
const comp = await figma.importComponentByKeyAsync("COMPONENT_KEY")
const instance = comp.createInstance()
instance.x = 40
instance.y = 40
figma.currentPage.appendChild(instance)
// Import a published component set by key and select a variant
const compSet = await figma.importComponentSetByKeyAsync("COMPONENT_SET_KEY")
const variant =
compSet.children.find((c) =>
c.type === "COMPONENT" && c.name.includes("size=md")
) || compSet.defaultVariant
const variantInstance = variant.createInstance()
variantInstance.x = 240
variantInstance.y = 40
figma.currentPage.appendChild(variantInstance)
return {
componentId: comp.id,
componentSetId: compSet.id,
placedInstanceIds: [instance.id, variantInstance.id]
}
```
## Component Set with Variable Modes (Full Pattern)
```js
await figma.loadFontAsync({ family: "Inter", style: "Medium" })
// 1. Create color collection with modes per variant
const colors = figma.variables.createVariableCollection("Component/Colors")
colors.renameMode(colors.modes[0].modeId, "primary")
const primaryMode = colors.modes[0].modeId
const secondaryMode = colors.addMode("secondary")
const bgVar = figma.variables.createVariable("bg", colors, "COLOR")
bgVar.setValueForMode(primaryMode, { r: 0, g: 0.4, b: 0.9, a: 1 })
bgVar.setValueForMode(secondaryMode, { r: 0, g: 0, b: 0, a: 0 })
const textVar = figma.variables.createVariable("text-color", colors, "COLOR")
textVar.setValueForMode(primaryMode, { r: 1, g: 1, b: 1, a: 1 })
textVar.setValueForMode(secondaryMode, { r: 0.1, g: 0.1, b: 0.1, a: 1 })
// 2. Create components with variable bindings
const modeMap = { primary: primaryMode, secondary: secondaryMode }
const components = []
for (const [variantName, modeId] of Object.entries(modeMap)) {
const comp = figma.createComponent()
comp.name = "variant=" + variantName
comp.layoutMode = "HORIZONTAL"
comp.primaryAxisAlignItems = "CENTER"
comp.counterAxisAlignItems = "CENTER"
comp.paddingLeft = 12; comp.paddingRight = 12
comp.layoutSizingHorizontal = "HUG"
comp.layoutSizingVertical = "HUG"
comp.cornerRadius = 6
// Bind background fill to variable
const bgPaint = figma.variables.setBoundVariableForPaint(
{ type: "SOLID", color: { r: 0, g: 0, b: 0 } }, "color", bgVar
)
comp.fills = [bgPaint]
// Add text with bound color
const label = figma.createText()
label.fontName = { family: "Inter", style: "Medium" }
label.characters = "Button"
label.fontSize = 14
const textPaint = figma.variables.setBoundVariableForPaint(
{ type: "SOLID", color: { r: 0, g: 0, b: 0 } }, "color", textVar
)
label.fills = [textPaint]
comp.appendChild(label)
// 3. CRITICAL: Set explicit mode so this variant renders correctly
comp.setExplicitVariableModeForCollection(colors, modeId)
components.push(comp)
}
// 4. Combine into component set
const componentSet = figma.combineAsVariants(components, figma.currentPage)
componentSet.name = "Button"
return {
componentSetId: componentSet.id,
colorCollectionId: colors.id
}
```
## Large ComponentSet with Variable Modes (Multi-Step Pattern)
For component sets with many variants (50+), split into multiple `use_figma` calls:
**Call 1: Create variable collections and return IDs**
```js
// Hex-to-0-1 helper
const hex = (h) => {
if (!h) return { r: 0, g: 0, b: 0, a: 0 }; // transparent
return {
r: parseInt(h.slice(1,3), 16) / 255,
g: parseInt(h.slice(3,5), 16) / 255,
b: parseInt(h.slice(5,7), 16) / 255,
a: 1
};
};
const coll = figma.variables.createVariableCollection("MyComponent/Colors");
coll.renameMode(coll.modes[0].modeId, "mode1");
const mode2Id = coll.addMode("mode2");
// Create variables from data map
const colorData = { "bg/default": ["#0B6BCB", "#636B74"], /* ... */ };
const modeOrder = ["mode1", "mode2"];
const modeIds = { mode1: coll.modes[0].modeId, mode2: mode2Id };
const varIds = {};
for (const [name, values] of Object.entries(colorData)) {
const v = figma.variables.createVariable(name, coll, "COLOR");
values.forEach((hex_val, i) => {
v.setValueForMode(modeIds[modeOrder[i]], hex_val ? hex(hex_val) : { r:0, g:0, b:0, a:0 });
});
varIds[name] = v.id;
}
// Return ALL IDs — needed by subsequent calls
return { collId: coll.id, modeIds, varIds };
```
**Call 2: Create components using stored IDs, combine and layout**
```js
await figma.loadFontAsync({ family: "Inter", style: "Semi Bold" });
// Paste IDs from Call 1 as literals
const collId = "VariableCollectionId:X:Y";
const modeIds = { mode1: "X:0", mode2: "X:1" };
const varIds = { /* ... from Call 1 ... */ };
const getVar = async (id) => await figma.variables.getVariableByIdAsync(id);
const bindColor = async (varId) => figma.variables.setBoundVariableForPaint(
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', await getVar(varId)
);
const collection = await figma.variables.getVariableCollectionByIdAsync(collId);
const components = [];
for (const mode of ["mode1", "mode2"]) {
for (const state of ["default", "hover"]) {
const comp = figma.createComponent();
comp.name = `mode=${mode}, state=${state}`;
comp.layoutMode = 'HORIZONTAL';
comp.primaryAxisAlignItems = 'CENTER';
comp.counterAxisAlignItems = 'CENTER';
comp.layoutSizingHorizontal = 'HUG';
comp.layoutSizingVertical = 'HUG';
comp.fills = [await bindColor(varIds[`bg/${state}`])];
comp.setExplicitVariableModeForCollection(collection, modeIds[mode]);
// ... add text children ...
components.push(comp);
}
}
// Combine — all children stack at (0,0)!
const cs = figma.combineAsVariants(components, figma.currentPage);
cs.name = "MyComponent";
// CRITICAL: layout variants in a structured grid mapped to variant axes.
const stateOrder = ["default", "hover"];
const modeOrder2 = ["mode1", "mode2"];
const colW = 140, rowH = 56;
for (const child of cs.children) {
const props = Object.fromEntries(
child.name.split(', ').map(p => p.split('='))
);
const col = stateOrder.indexOf(props.state);
const row = modeOrder2.indexOf(props.mode);
child.x = col * colW;
child.y = row * rowH;
}
// Resize from actual child bounds
let maxX = 0, maxY = 0;
for (const child of cs.children) {
maxX = Math.max(maxX, child.x + child.width);
maxY = Math.max(maxY, child.y + child.height);
}
cs.resizeWithoutConstraints(maxX + 40, maxY + 40);
// Wrap in section
const section = figma.createSection();
section.name = "MyComponent Section";
section.appendChild(cs);
section.resizeWithoutConstraints(cs.width + 200, cs.height + 200);
return { csId: cs.id, count: components.length };
```
## Read Existing Nodes and Return Data
```js
const page = figma.currentPage
const nodes = page.findAll(n => n.type === 'FRAME')
const data = nodes.map(n => ({
id: n.id,
name: n.name,
width: n.width,
height: n.height,
childCount: n.children?.length || 0
}))
return { frames: data }
```
@@ -0,0 +1,472 @@
# Component & Variant API Patterns
> Part of the [use_figma skill](../SKILL.md). How to correctly use the Plugin API for components, variants, and component properties.
>
> For design system context (when to use variants vs properties, code-to-Figma translation, property model), see [wwds-components](working-with-design-systems/wwds-components.md).
## Contents
- Creating a Component
- Combining Components into a Component Set (Variants)
- Laying Out Variants After combineAsVariants (Required)
- Component Properties: addComponentProperty API
- Linking Properties to Child Nodes (Required)
- INSTANCE_SWAP: Avoiding Variant Explosion
- Discovering Existing Conventions in the File
- Importing Components by Key
- Working with Instances (finding variants, setProperties, text overrides, detachInstance)
## Creating a Component
`figma.createComponent()` returns a `ComponentNode`, which behaves like a `FrameNode` but can be published, instanced, and combined into variant sets.
```javascript
const comp = figma.createComponent();
comp.name = "MyComponent";
comp.layoutMode = "HORIZONTAL";
comp.primaryAxisAlignItems = "CENTER";
comp.counterAxisAlignItems = "CENTER";
comp.paddingLeft = 12;
comp.paddingRight = 12;
comp.layoutSizingHorizontal = "HUG";
comp.layoutSizingVertical = "HUG";
comp.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.36, b: 0.96 } }];
```
## Combining Components into a Component Set (Variants)
`figma.combineAsVariants(components, parent)` takes an array of `ComponentNode`s (not frames — frames will throw) and groups them into a `ComponentSetNode`.
Variant names use a `Property=Value` format. Every unique combination must exist as a child component — missing ones show as blank gaps in the variant picker.
```javascript
// Each component's name encodes its variant properties
const comp1 = figma.createComponent();
comp1.name = "size=md, style=primary";
const comp2 = figma.createComponent();
comp2.name = "size=md, style=secondary";
const componentSet = figma.combineAsVariants([comp1, comp2], figma.currentPage);
componentSet.name = "Button";
```
**Before creating variants, inspect the file** for existing naming patterns. Different files use different conventions (`State=Default` vs `state=default` vs `State/Default`). Always match what's already there.
## Laying Out Variants After combineAsVariants (Required)
After `combineAsVariants`, all children stack at `(0, 0)`. You **must** position them or the component set will appear as a single collapsed element with all variants overlapping.
```javascript
const cs = figma.combineAsVariants(components, figma.currentPage);
// Simple row layout
cs.children.forEach((child, i) => {
child.x = i * 150;
child.y = 0;
});
// CRITICAL: resize the component set from actual child bounds
let maxX = 0, maxY = 0;
for (const child of cs.children) {
maxX = Math.max(maxX, child.x + child.width);
maxY = Math.max(maxY, child.y + child.height);
}
cs.resizeWithoutConstraints(maxX + 40, maxY + 40);
```
For multi-axis variants (e.g., size × style × state), parse the child's name to determine grid position:
```javascript
for (const child of cs.children) {
const props = Object.fromEntries(
child.name.split(', ').map(p => p.split('='))
);
const col = stateValues.indexOf(props.state);
const row = styleValues.indexOf(props.style);
child.x = col * colWidth;
child.y = row * rowHeight;
}
```
## Component Properties: addComponentProperty API
`addComponentProperty` adds a TEXT, BOOLEAN, or INSTANCE_SWAP property to a component. It returns a **string key** (e.g., `"label#4:0"`) — never hardcode or guess this key.
```javascript
// Returns the key as a string — capture it!
const labelKey = comp.addComponentProperty('Label', 'TEXT', 'Default text');
const showIconKey = comp.addComponentProperty('Show Icon', 'BOOLEAN', true);
const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', iconComponentId);
```
**Timing**: Add component properties to each variant component **before** calling `combineAsVariants`. After combining, the component set inherits all properties from its children. Do not add properties to the `ComponentSetNode` directly.
## Linking Properties to Child Nodes (Required)
A property that is added but not linked to a child node does **nothing**. You must set `componentPropertyReferences` on the child:
```javascript
// TEXT property → link to a text node's characters
const labelKey = comp.addComponentProperty('Label', 'TEXT', 'Button');
const textNode = figma.createText();
textNode.characters = "Button";
comp.appendChild(textNode);
textNode.componentPropertyReferences = { characters: labelKey };
// BOOLEAN + INSTANCE_SWAP → link to an instance node
const showIconKey = comp.addComponentProperty('Show Icon', 'BOOLEAN', true);
const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', iconComp.id);
const iconInstance = iconComp.createInstance();
comp.appendChild(iconInstance);
iconInstance.componentPropertyReferences = {
visible: showIconKey, // BOOLEAN controls show/hide
mainComponent: iconSlotKey // INSTANCE_SWAP controls which component
};
```
**Valid `componentPropertyReferences` keys:**
- `characters` — TEXT property on a TextNode
- `visible` — BOOLEAN property (any node)
- `mainComponent` — INSTANCE_SWAP property on an InstanceNode
## INSTANCE_SWAP: Avoiding Variant Explosion
When a component has many possible sub-elements (e.g., 30 different icons), **never** create a variant per sub-element. Use a single INSTANCE_SWAP property instead — the user picks from any compatible component at design time.
```javascript
// Create icon as its own ComponentNode
const iconComp = figma.createComponent();
iconComp.name = "Icon/Search";
iconComp.resize(24, 24);
const svgNode = figma.createNodeFromSvg('<svg>...</svg>');
iconComp.appendChild(svgNode);
// Use it as the default for INSTANCE_SWAP
const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', iconComp.id);
const instance = iconComp.createInstance();
comp.appendChild(instance);
instance.componentPropertyReferences = { mainComponent: iconSlotKey };
```
This works for icons, avatars, badges, or any swappable nested element.
## Discovering Existing Conventions in the File
**Always inspect the file before creating components.** Different files have different naming styles, structures, and conventions. Your code should match what's already there.
### List all existing components across all pages
```javascript
const results = [];
for (const page of figma.root.children) {
await figma.setCurrentPageAsync(page);
page.findAll(n => {
if (n.type === 'COMPONENT') results.push(`[${page.name}] ${n.name} (COMPONENT) id=${n.id}`);
if (n.type === 'COMPONENT_SET') results.push(`[${page.name}] ${n.name} (COMPONENT_SET) id=${n.id}`);
return false;
});
}
return results.join('\n');
```
### Inspect an existing component set's variant naming pattern
```javascript
const cs = await figma.getNodeByIdAsync('COMPONENT_SET_ID');
const variantNames = cs.children.map(c => c.name);
const propDefs = cs.componentPropertyDefinitions;
return { variantNames, propDefs };
```
### Find existing components in the file
```javascript
const components = [];
for (const page of figma.root.children) {
await figma.setCurrentPageAsync(page);
page.findAll(n => {
if (n.type === 'COMPONENT') {
components.push({ name: n.name, id: n.id, page: page.name, w: n.width, h: n.height });
}
return false;
});
}
return components;
```
## Importing Components by Key (Team Libraries)
`importComponentByKeyAsync` and `importComponentSetByKeyAsync` import components from **team libraries** (not the same file you're working in). For components in the current file, use `figma.getNodeByIdAsync()` or `findOne()`/`findAll()` to locate them directly.
```javascript
// Import a component from a team library
const comp = await figma.importComponentByKeyAsync("COMPONENT_KEY");
const instance = comp.createInstance();
// Import a component set from a team library and pick a variant
const set = await figma.importComponentSetByKeyAsync("COMPONENT_SET_KEY");
const variant = set.children.find(c =>
c.type === "COMPONENT" && c.name.includes("size=md")
) || set.defaultVariant;
const variantInstance = variant.createInstance();
```
## Working with Instances
### Finding the right variant in a component set
Parse variant names to match on multiple properties simultaneously:
```javascript
const compSet = await figma.importComponentSetByKeyAsync("KEY");
const variant = compSet.children.find(c => {
const props = Object.fromEntries(
c.name.split(', ').map(p => p.split('='))
);
return props.variant === "primary" && props.size === "md";
}) || compSet.defaultVariant;
const instance = variant.createInstance();
```
### Setting variant properties on an instance
After creating an instance from a component set, you can set variant properties via `setProperties`:
```javascript
const instance = defaultVariant.createInstance();
instance.setProperties({
"variant": "primary",
"size": "medium"
});
```
### Overriding text in a component instance
**Always discover component properties BEFORE writing text overrides.** Components expose text as `TEXT`-type component properties, and `setProperties()` is the correct way to override them. Direct `node.characters` changes on property-managed text may be overridden by the component property system on render.
**Step 1: Inspect componentProperties on a sample instance:**
```javascript
const instance = comp.createInstance();
const propDefs = instance.componentProperties;
// Returns e.g.: { "Label#2:0": { type: "TEXT", value: "Button" }, "Has Icon#4:64": { type: "BOOLEAN", value: true } }
return propDefs;
```
Also check nested instances — a parent component may not expose text properties directly, but its nested child instances might:
```javascript
const nestedInstances = instance.findAll(n => n.type === "INSTANCE");
const nestedProps = nestedInstances.map(ni => ({
name: ni.name,
id: ni.id,
properties: ni.componentProperties
}));
```
**Step 2: Use setProperties() for TEXT-type properties:**
```javascript
const instance = comp.createInstance();
const propDefs = instance.componentProperties;
for (const [key, def] of Object.entries(propDefs)) {
if (def.type === "TEXT") {
instance.setProperties({ [key]: "New text value" });
}
}
```
For nested instances that expose their own TEXT properties, call `setProperties()` on the nested instance:
```javascript
const nestedHeading = instance.findOne(n => n.type === "INSTANCE" && n.name === "Text Heading");
if (nestedHeading) {
nestedHeading.setProperties({ "Text#2104:5": "Actual heading text" });
}
```
**Step 3: Only fall back to direct node.characters for unmanaged text.** If text is NOT controlled by any component property, find text nodes directly. **Always load the node's actual font first** — instance text nodes inherit fonts from the source component, so don't assume Inter Regular:
```javascript
const textNodes = instance.findAll(n => n.type === "TEXT");
for (const t of textNodes) {
await figma.loadFontAsync(t.fontName);
t.characters = "Updated text";
}
```
### detachInstance() invalidates ancestor node IDs
**Warning:** When `detachInstance()` is called on a nested instance inside a library component instance, the parent instance may also get implicitly detached (converted from INSTANCE to FRAME with a **new ID**). Subsequent `getNodeByIdAsync(oldParentId)` returns null.
```javascript
// WRONG — cached parent ID becomes invalid after child detach
const parentId = parentInstance.id;
nestedChild.detachInstance();
const parent = await figma.getNodeByIdAsync(parentId); // null!
// CORRECT — re-discover nodes by traversal from a stable (non-instance) parent
const stableFrame = await figma.getNodeByIdAsync(manualFrameId); // a frame YOU created
nestedChild.detachInstance();
// Re-find the parent by traversing from the stable frame
const parent = stableFrame.findOne(n => n.name === "ParentName");
```
If you must detach multiple nested instances across sibling components, do it in a **single** `use_figma` call — discover all targets by traversal at the start before any detachment mutates the tree.
## Inspecting Component Metadata (Deep Traversal)
These helpers extract the full property schema and descendant structure of a component. Useful for understanding complex components before creating instances or setting properties.
```javascript
/**
* Imports a component or component set from a library by its published key.
* Tries COMPONENT first, then falls back to COMPONENT_SET.
*
* @param {string} componentKey - The published key of the component or component set.
* @returns {Promise<ComponentNode|ComponentSetNode>}
*/
async function importComponentByKey(componentKey) {
try {
return await figma.importComponentByKeyAsync(componentKey);
} catch {
try {
return await figma.importComponentSetByKeyAsync(componentKey);
} catch {
throw new Error(`No Component or Component Set available with key '${componentKey}'`);
}
}
}
/**
* Given a main component node, returns the component set parent if one exists,
* otherwise returns the component itself. Used to get the top-level node that
* holds `componentPropertyDefinitions`.
*
* @param {ComponentNode} mainComponent
* @returns {ComponentNode|ComponentSetNode}
*/
function getRelevantComponentNode(mainComponent) {
return mainComponent.parent.type === "COMPONENT_SET"
? mainComponent.parent
: mainComponent;
}
/**
* Extracts `componentPropertyDefinitions` from a component or component set node
* into a flat map keyed by property key.
*
* @param {ComponentNode|ComponentSetNode} node
* @returns {Record<string, {name: string, type: string, key: string, variantOptions?: string[]}>}
*/
function getComponentProps(node) {
const result = {};
for (let key in node.componentPropertyDefinitions) {
const prop = {
name: key.replace(/#[^#]+$/, ""),
type: node.componentPropertyDefinitions[key].type,
key: key
};
if (prop.type === "VARIANT") {
prop.variantOptions = node.componentPropertyDefinitions[key].variantOptions;
}
result[key] = prop;
}
return result;
}
/**
* Recursively walks a component tree and collects all INSTANCE and TEXT nodes
* into `result`, keyed by `TYPE[name]`. Handles variant namespacing and
* deduplicates nodes with identical names but differing property references.
*
* @param {SceneNode} node - The node to traverse.
* @param {string[]} namespace - Accumulated variant names for the current path.
* @param {Record<string, object>} result - Accumulator object populated in place.
*/
function collectDescendants(node, namespace, result) {
if (node.type === "INSTANCE" || node.type === "TEXT") {
const references = node.componentPropertyReferences || {};
if (!node.visible && !references.visible) return;
const object = { type: node.type, name: node.name, references };
let key = `${node.type}[${node.name}]`;
if (result[key] && JSON.stringify(references) !== JSON.stringify(result[key].references)) {
key += btoa(btoa(unescape(encodeURIComponent(JSON.stringify(references)))));
}
if (node.type === "INSTANCE") {
const mainComponent = getRelevantComponentNode(node.mainComponent);
object.properties = getComponentProps(mainComponent);
object.descendants = {};
object.mainComponentName = mainComponent.name;
collectDescendants(mainComponent, [], object.descendants);
}
const start = namespace.length ? { variants: [] } : {};
result[key] = Object.assign(object, result[key] || start);
if (namespace.length) result[key].variants.push(namespace[namespace.length - 1]);
} else if ("children" in node && node.visible) {
if (node.type === "COMPONENT" && node.parent.type === "COMPONENT_SET") namespace.push(node.name);
node.children.forEach(child => collectDescendants(child, namespace, result));
}
}
/**
* Returns structured metadata for a component or component set defined in the current file.
*
* @param {string} componentId - The node ID of a COMPONENT or COMPONENT_SET node.
* @returns {Promise<{name: string, nodeId: string, properties: object, descendants: object}|undefined>}
*/
async function getLocalComponentMetadata(componentId) {
const node = await figma.getNodeByIdAsync(componentId);
if (node.type === "COMPONENT_SET" || node.type === "COMPONENT") {
const result = {
name: node.name,
nodeId: node.id,
properties: {},
descendants: {}
};
result.properties = getComponentProps(node);
collectDescendants(node, [], result.descendants);
return result;
} else {
throw new Error("Node is not a Component or Component Set");
}
}
/**
* Returns structured metadata for a published component or component set loaded by its key.
*
* @param {string} componentKey - The published key of the component or component set.
* @returns {Promise<{name: string, nodeId: string, properties: object, descendants: object}>}
*/
async function getPublishedComponentMetadata(componentKey) {
const node = await importComponentByKey(componentKey);
const result = {
name: node.name,
nodeId: node.id,
properties: {},
descendants: {}
};
result.properties = getComponentProps(node);
collectDescendants(node, [], result.descendants);
return result;
}
```
### Full metadata extraction script
```javascript
// For local components, use getLocalComponentMetadata:
const result = await getLocalComponentMetadata('COMPONENT_OR_SET_ID');
return result;
// For published components, use getPublishedComponentMetadata:
// const result = await getPublishedComponentMetadata('COMPONENT_KEY');
// return result;
```
@@ -0,0 +1,125 @@
# Effect Style API Patterns
> Part of the [use_figma skill](../SKILL.md). How to create, apply, and inspect effect styles using the Plugin API.
>
> For design system context (effect types, variable bindings on effects, gotchas), see [wwds-effect-styles](working-with-design-systems/wwds-effect-styles.md).
## Contents
- Listing Effect Styles
- Creating a Drop Shadow Style
- Importing Library Effect Styles
- Applying Effect Styles to Nodes
## Listing Effect Styles
```javascript
/**
* Lists all local effect styles.
*
* @returns {Promise<Array<{id: string, name: string, key: string, effectCount: number}>>}
*/
async function listEffectStyles() {
const styles = await figma.getLocalEffectStylesAsync();
return styles.map(s => ({
id: s.id,
name: s.name,
key: s.key,
effectCount: s.effects.length
}));
}
```
Full runnable script:
```javascript
const results = await listEffectStyles();
return results;
```
## Creating a Drop Shadow Style
Colors are **RGBA 01 range**. `effects` is a read-only array — always reassign, never mutate in place.
```javascript
/**
* Creates a drop shadow effect style.
*
* @param {string} name - e.g. "Elevation/200"
* @param {{ r: number, g: number, b: number, a: number }} color - RGBA, 0-1 range
* @param {{ x: number, y: number }} offset
* @param {number} radius - blur radius
* @param {number} [spread=0]
* @returns {EffectStyle}
*/
function createDropShadowStyle(name, color, offset, radius, spread) {
const style = figma.createEffectStyle();
style.name = name;
style.effects = [{
type: "DROP_SHADOW",
color,
offset,
radius,
spread: spread || 0,
visible: true,
blendMode: "NORMAL"
}];
return style;
}
```
Full runnable script:
```javascript
const style = createDropShadowStyle(
"Elevation/200",
{ r: 0, g: 0, b: 0, a: 0.15 },
{ x: 0, y: 4 },
12,
0
);
return { id: style.id, name: style.name };
```
## Importing Library Effect Styles
For effect styles from **team libraries**, use `importStyleByKeyAsync`:
```javascript
// Import a library effect style by key
const shadowStyle = await figma.importStyleByKeyAsync("EFFECT_STYLE_KEY");
// Apply to a node
node.effectStyleId = shadowStyle.id;
```
`search_design_system` with `includeStyles: true` returns style keys you can import this way. Prefer importing library styles over creating new ones.
## Applying Effect Styles to Nodes
```javascript
/**
* Applies an effect style to all nodes on the current page that match a given name pattern.
*
* @param {string} styleId - The ID of an EffectStyle.
* @param {string} nodeNamePattern - Substring match against node names.
* @returns {number} - Number of nodes the style was applied to.
*/
function applyEffectStyleToMatchingNodes(styleId, nodeNamePattern) {
const nodes = figma.currentPage.findAll(n => n.name.includes(nodeNamePattern));
let applied = 0;
for (const node of nodes) {
if ('effectStyleId' in node) {
node.effectStyleId = styleId;
applied++;
}
}
return applied;
}
```
Full runnable script:
```javascript
const applied = applyEffectStyleToMatchingNodes('STYLE_ID', 'Card');
return { applied };
```
@@ -0,0 +1,622 @@
# Gotchas & Common Mistakes
> Part of the [use_figma skill](../SKILL.md). Every known pitfall with WRONG/CORRECT code examples.
## Contents
- Component properties and variant creation pitfalls
- Paint, color, and variable binding pitfalls
- Page context and plugin lifecycle pitfalls
- Auto Layout and sizing order pitfalls (including HUG/FILL interactions)
- Variant layout and geometry pitfalls
- Variable scopes and mode pitfalls
- Node cleanup and empty-fill pitfalls
- detachInstance() and node ID invalidation
## New nodes default to (0,0) and overlap existing content
Every `figma.create*()` call places the node at position (0,0). If you append multiple nodes directly to the page, they all stack on top of each other and on top of any existing content.
**This only matters for nodes appended directly to the page** (i.e., top-level nodes). Nodes appended as children of other frames, components, or auto-layout containers are positioned by their parent — don't scan for overlaps when nesting nodes.
```js
// WRONG — top-level node lands at (0,0), overlapping existing page content
const frame = figma.createFrame()
frame.name = "My New Frame"
frame.resize(400, 300)
figma.currentPage.appendChild(frame)
// CORRECT — find existing content bounds and place the new top-level node to the right
const page = figma.currentPage
let maxX = 0
for (const child of page.children) {
const right = child.x + child.width
if (right > maxX) maxX = right
}
const frame = figma.createFrame()
frame.name = "My New Frame"
frame.resize(400, 300)
figma.currentPage.appendChild(frame)
frame.x = maxX + 100 // 100px gap from rightmost existing content
frame.y = 0
// NOT NEEDED — child nodes inside a parent don't need overlap scanning
const card = figma.createFrame()
card.layoutMode = 'VERTICAL'
const label = figma.createText()
card.appendChild(label) // positioned by auto-layout, no x/y needed
```
## `addComponentProperty` returns a string key, not an object — never hardcode or guess it
Figma generates the property key dynamically (e.g. `"label#4:0"`). The suffix is unpredictable. Always capture and use the return value directly.
```js
// WRONG — guessing / hardcoding the key
comp.addComponentProperty('label', 'TEXT', 'Button')
labelNode.componentPropertyReferences = { characters: 'label#0:1' } // Error: key not found
// WRONG — treating the return value as an object
const result = comp.addComponentProperty('Label', 'TEXT', 'Button')
const propKey = Object.keys(result)[0] // BUG: returns '0' (first char index of string!)
labelNode.componentPropertyReferences = { characters: propKey } // Error: property '0' not found
// CORRECT — the return value IS the key string, use it directly
const propKey = comp.addComponentProperty('Label', 'TEXT', 'Button')
// propKey === "label#4:0" (exact value varies; never assume it)
labelNode.componentPropertyReferences = { characters: propKey }
```
The same applies to `COMPONENT_SET` nodes — `addComponentProperty` always returns the property key as a string.
## MUST return ALL created/mutated node IDs
Every script that creates or mutates nodes on the canvas must track and return all affected node IDs in the return value. Without these IDs, subsequent calls cannot reference, validate, or clean up those nodes.
```js
// WRONG — only returns the parent frame ID, loses track of children
const frame = figma.createFrame()
const rect = figma.createRectangle()
const text = figma.createText()
frame.appendChild(rect)
frame.appendChild(text)
return { nodeId: frame.id }
// CORRECT — returns all created node IDs in a structured response
const frame = figma.createFrame()
const rect = figma.createRectangle()
const text = figma.createText()
frame.appendChild(rect)
frame.appendChild(text)
return {
createdNodeIds: [frame.id, rect.id, text.id],
rootNodeId: frame.id
}
// CORRECT — when mutating existing nodes, return those IDs too
const nodes = figma.currentPage.findAll(n => n.name === 'Card')
for (const n of nodes) {
n.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
}
return {
mutatedNodeIds: nodes.map(n => n.id),
count: nodes.length
}
```
## Colors are 01 range
```js
// WRONG — will throw validation error (ZeroToOne enforced)
node.fills = [{ type: 'SOLID', color: { r: 255, g: 0, b: 0 } }]
// CORRECT
node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
```
## Fills/strokes are immutable arrays
```js
// WRONG — modifying in place does nothing
node.fills[0].color = { r: 1, g: 0, b: 0 }
// CORRECT — clone, modify, reassign
const fills = JSON.parse(JSON.stringify(node.fills))
fills[0].color = { r: 1, g: 0, b: 0 }
node.fills = fills
```
## setBoundVariableForPaint returns a NEW paint
```js
// WRONG — ignoring return value
figma.variables.setBoundVariableForPaint(paint, "color", colorVar)
node.fills = [paint] // paint is unchanged!
// CORRECT — capture the returned new paint
const boundPaint = figma.variables.setBoundVariableForPaint(paint, "color", colorVar)
node.fills = [boundPaint]
```
## Variable collection starts with 1 mode
```js
// A new collection already has one mode — rename it, don't try to add first
const collection = figma.variables.createVariableCollection("Colors")
// collection.modes = [{ modeId: "...", name: "Mode 1" }]
collection.renameMode(collection.modes[0].modeId, "Light")
const darkModeId = collection.addMode("Dark")
```
## combineAsVariants requires ComponentNodes
```js
// WRONG — passing frames
const f1 = figma.createFrame()
figma.combineAsVariants([f1], figma.currentPage) // Error!
// CORRECT — passing components
const c1 = figma.createComponent()
c1.name = "variant=primary, size=md"
const c2 = figma.createComponent()
c2.name = "variant=secondary, size=md"
figma.combineAsVariants([c1, c2], figma.currentPage)
```
## Page switching: sync setter throws
The sync setter `figma.currentPage = page` **throws an error** in `use_figma` runtimes (MCP, evals, assistant). Use `await figma.setCurrentPageAsync(page)` instead — it switches the page and loads its content.
```js
// WRONG — throws "Setting figma.currentPage is not supported in this runtime"
figma.currentPage = targetPage
// CORRECT — async method switches and loads content
await figma.setCurrentPageAsync(targetPage)
```
## `get_metadata` only sees one page — use `use_figma` to discover all pages
A Figma file can have multiple pages (canvas nodes). `get_metadata` operates on a single node/page — it cannot scan the entire document. To discover all pages and their top-level contents, use `use_figma`:
```js
// WRONG — calling get_metadata with the file root or expecting it to list all pages
// get_metadata only returns the subtree of the node you pass it
// CORRECT — use use_figma to list pages, then inspect each one
const pages = figma.root.children.map(p => `${p.name} id=${p.id} children=${p.children.length}`);
return pages.join('\n');
```
Icons, variables, and components may live on pages other than the first. Always enumerate all pages before concluding that the file has no existing assets.
## Never use figma.notify()
```js
// WRONG — throws "not implemented" error
figma.notify("Done!")
// CORRECT — return a value to send data back to the agent
return "Done!"
```
## `getPluginData()` / `setPluginData()` are not supported
These APIs are not available in the `use_figma` runtime. Use `getSharedPluginData()` / `setSharedPluginData()` instead (these ARE supported), or track nodes by returning IDs.
```js
// WRONG — not supported in use_figma
node.setPluginData('my_key', 'my_value')
const val = node.getPluginData('my_key')
// CORRECT — use shared plugin data (requires a namespace)
node.setSharedPluginData('my_namespace', 'my_key', 'my_value')
const val = node.getSharedPluginData('my_namespace', 'my_key')
// ALSO CORRECT — return node IDs and track them across calls
const rect = figma.createRectangle()
return { nodeId: rect.id }
// Then pass nodeId as a string literal in the next use_figma call
```
## Script must always return a value
```js
// WRONG — no return, caller gets no useful response
figma.createRectangle()
// CORRECT — return a result (objects are auto-serialized, errors are auto-captured)
const rect = figma.createRectangle()
return { nodeId: rect.id }
```
## setBoundVariable for paint fields only works on SOLID paints
```js
// Only SOLID paint type supports color variable binding
// Gradient paints, image paints, etc. will throw
const solidPaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }
const bound = figma.variables.setBoundVariableForPaint(solidPaint, "color", colorVar)
```
## Explicit variable modes must be set per component
```js
// WRONG — all variants render with the default (first) mode
const colorCollection = figma.variables.createVariableCollection("Colors")
// ... create variables and modes ...
// Components all show the first mode's values by default!
// CORRECT — set explicit mode on each component to get variant-specific values
component.setExplicitVariableModeForCollection(colorCollection, targetModeId)
```
## `TextStyle.setBoundVariable` is not available in headless use_figma
`setBoundVariable` exists on `TextStyle` in the typed API but is **not available** when running scripts through `use_figma` (MCP, headless assistant mode). Calling it will throw `"not a function"`.
```js
// WRONG — throws "not a function" in use_figma / headless
const ts = figma.createTextStyle()
ts.setBoundVariable("fontSize", fontSizeVar)
// CORRECT (headless) — set raw values; bind variables interactively in Figma later
const ts = figma.createTextStyle()
ts.fontSize = 24
```
This only affects `TextStyle`. Variable binding on **nodes** (`node.setBoundVariable(...)`) and on **paint objects** (`figma.variables.setBoundVariableForPaint(...)`) still works in headless mode as expected.
If live variable binding on text styles is required, create the styles with raw values via `use_figma`, then bind variables interactively through the Figma Styles panel or a full interactive plugin.
## `lineHeight` and `letterSpacing` must be objects, not bare numbers
```js
// WRONG — throws or silently does nothing
style.lineHeight = 1.5
style.lineHeight = 24
style.letterSpacing = 0
// CORRECT
style.lineHeight = { unit: "AUTO" } // auto/intrinsic
style.lineHeight = { value: 24, unit: "PIXELS" } // fixed pixel height
style.lineHeight = { value: 150, unit: "PERCENT" } // percentage of font size
style.letterSpacing = { value: 0, unit: "PIXELS" } // no tracking
style.letterSpacing = { value: -0.5, unit: "PIXELS" } // tight
style.letterSpacing = { value: 5, unit: "PERCENT" } // percent-based
```
This applies to both `TextStyle` and `TextNode` properties. The same rule applies inside `use_figma`, interactive plugins, and any other plugin API context.
## Font style names are file-dependent — probe before assuming
Font style names vary per provider and per Figma file. `"SemiBold"` and `"Semi Bold"` are different strings. Loading a font with the wrong style string **throws silently or errors** — there is no canonical list.
```js
// WRONG — guessing style names
await figma.loadFontAsync({ family: "Inter", style: "SemiBold" }) // may throw
// CORRECT — probe which style names are available
const candidates = ["SemiBold", "Semi Bold", "Semibold"]
for (const style of candidates) {
try {
await figma.loadFontAsync({ family: "Inter", style })
// capture the one that works
break
} catch (_) {}
}
```
When building a type ramp script, always verify font styles against the target file before hardcoding them.
## combineAsVariants does NOT auto-layout in headless mode
```js
// WRONG — all variants stack at position (0, 0), resulting in a tiny ComponentSet
const components = [comp1, comp2, comp3]
const cs = figma.combineAsVariants(components, figma.currentPage)
// cs.width/height will be the size of a SINGLE variant!
// CORRECT — manually layout children in a grid after combining
const cs = figma.combineAsVariants(components, figma.currentPage)
const colWidth = 120
const rowHeight = 56
cs.children.forEach((child, i) => {
const col = i % numCols
const row = Math.floor(i / numCols)
child.x = col * colWidth
child.y = row * rowHeight
})
// CRITICAL: resize from actual child bounds, not formula — formula errors leave variants outside the boundary
let maxX = 0, maxY = 0
for (const child of cs.children) {
maxX = Math.max(maxX, child.x + child.width)
maxY = Math.max(maxY, child.y + child.height)
}
cs.resizeWithoutConstraints(maxX + 40, maxY + 40)
```
## COLOR variable values use {r, g, b, a} (with alpha)
```js
// Paint colors use {r, g, b} (no alpha — opacity is a separate paint property)
node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
// But COLOR variable values use {r, g, b, a} — alpha maps to paint opacity
const colorVar = figma.variables.createVariable("bg", collection, "COLOR")
colorVar.setValueForMode(modeId, { r: 1, g: 0, b: 0, a: 1 }) // opaque red
colorVar.setValueForMode(modeId, { r: 0, g: 0, b: 0, a: 0 }) // fully transparent
// ⚠️ Don't confuse: {r, g, b} for paint colors vs {r, g, b, a} for variable values
```
## `layoutSizingVertical`/`layoutSizingHorizontal` = `'FILL'` requires auto-layout parent FIRST
```js
// WRONG — setting FILL before the node is a child of an auto-layout frame
const child = figma.createFrame()
child.layoutSizingVertical = 'FILL' // ERROR: "FILL can only be set on children of auto-layout frames"
parent.appendChild(child)
// CORRECT — append to auto-layout parent FIRST, then set FILL
const child = figma.createFrame()
parent.appendChild(child) // parent must have layoutMode set
child.layoutSizingVertical = 'FILL' // Works!
```
## HUG parents collapse FILL children
A `HUG` parent cannot give `FILL` children meaningful size. If children have `layoutSizingHorizontal = "FILL"` but the parent is `"HUG"`, the children collapse to minimum size. The parent must be `"FILL"` or `"FIXED"` for FILL children to expand. This is a common cause of truncated text in select fields, inputs, and action rows.
```js
// WRONG — parent hugs, so FILL children get zero extra space
const parent = figma.createFrame()
parent.layoutMode = 'HORIZONTAL'
parent.layoutSizingHorizontal = 'HUG'
const child = figma.createFrame()
parent.appendChild(child)
child.layoutSizingHorizontal = 'FILL' // collapses to min size!
// CORRECT — parent must be FIXED or FILL for FILL children to expand
const parent = figma.createFrame()
parent.layoutMode = 'HORIZONTAL'
parent.resize(400, 50)
parent.layoutSizingHorizontal = 'FIXED' // or 'FILL' if inside another auto-layout
const child = figma.createFrame()
parent.appendChild(child)
child.layoutSizingHorizontal = 'FILL' // expands to fill remaining 400px
```
## `layoutGrow` with a hugging parent causes content compression
```js
// WRONG — layoutGrow on a child when parent has primaryAxisSizingMode='AUTO' (hug)
// causes the child to SHRINK below its natural size instead of expanding
const parent = figma.createComponent()
parent.layoutMode = 'VERTICAL'
parent.primaryAxisSizingMode = 'AUTO' // hug contents
const content = figma.createFrame()
content.layoutMode = 'VERTICAL'
content.primaryAxisSizingMode = 'AUTO'
parent.appendChild(content)
content.layoutGrow = 1 // BUG: content compresses, children hidden!
// CORRECT — only use layoutGrow when parent has FIXED sizing with extra space
content.layoutGrow = 0 // let content take its natural size
// OR: set parent to FIXED sizing first
parent.primaryAxisSizingMode = 'FIXED'
parent.resizeWithoutConstraints(300, 500)
content.layoutGrow = 1 // NOW it correctly fills remaining space
```
## `resize()` resets `primaryAxisSizingMode` and `counterAxisSizingMode` to FIXED
`resize(w, h)` silently resets **both** sizing modes to `FIXED`. If you call it after setting `HUG`, the frame locks to the exact pixel value you passed — even a throwaway like `1`.
```js
// WRONG — resize() after setting sizing mode overwrites it back to FIXED
const frame = figma.createComponent()
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO' // hug height
frame.counterAxisSizingMode = 'FIXED'
frame.resize(300, 10) // BUG: resets BOTH axes to 'FIXED'! Height stays at 10px forever.
// ESPECIALLY DANGEROUS — throwaway values when you only care about one axis
const comp = figma.createComponent()
comp.layoutMode = 'VERTICAL'
comp.layoutSizingHorizontal = 'FIXED'
comp.layoutSizingVertical = 'HUG'
comp.resize(280, 1) // BUG: "I only want width=280" but this locks height to 1px!
// HUG was reset to FIXED by resize(), frame is now permanently 280×1
// CORRECT — call resize() FIRST, then set sizing modes
const frame = figma.createComponent()
frame.layoutMode = 'VERTICAL'
frame.resize(300, 40) // use a reasonable default, never 0 or 1
frame.counterAxisSizingMode = 'FIXED' // keep width fixed at 300
frame.primaryAxisSizingMode = 'AUTO' // NOW set height to hug — this sticks!
// Or use the modern shorthand (equivalent):
// frame.layoutSizingHorizontal = 'FIXED'
// frame.layoutSizingVertical = 'HUG'
```
**Rule of thumb**: Never pass a throwaway/garbage value (like `1` or `0`) to `resize()` for an axis you intend to be `HUG`. Either call `resize()` before setting sizing modes, or use a reasonable default that won't cause visual bugs if the mode reset goes unnoticed.
## Node positions don't auto-reset after reparenting
```js
// WRONG — assuming positions reset when moving a node into a new parent
const node = figma.createRectangle()
node.x = 500; node.y = 500;
figma.currentPage.appendChild(node)
section.appendChild(node) // node still at (500, 500) relative to section!
// CORRECT — explicitly set x/y after ANY reparenting operation
section.appendChild(node)
node.x = 80; node.y = 80; // reset to desired position within section
```
## Grid layout with mixed-width rows causes overlaps
```js
// WRONG — using a single column offset for rows with different-width items
// e.g. vertical cards (320px) and horizontal cards (500px) in a 2-row grid
for (let i = 0; i < allCards.length; i++) {
allCards[i].x = (i % 4) * 370 // 370 works for 320px cards but NOT 500px cards!
}
// CORRECT — compute each row's spacing independently based on actual child widths
const gap = 50
let x = 0
for (const card of horizontalCards) {
card.x = x
x += card.width + gap // use actual width, not a fixed column size
}
```
## Sections don't auto-resize to fit content
```js
// WRONG — section stays at default size, content overflows
const section = figma.createSection()
section.name = "My Section"
section.appendChild(someNode) // node may be outside section bounds
// CORRECT — explicitly resize after adding content
const section = figma.createSection()
section.name = "My Section"
section.appendChild(someNode)
section.resizeWithoutConstraints(
Math.max(someNode.width + 100, 800),
Math.max(someNode.height + 100, 600)
)
```
## `counterAxisAlignItems` does NOT support `'STRETCH'`
```js
// WRONG — 'STRETCH' is not a valid enum value
comp.counterAxisAlignItems = 'STRETCH'
// Error: Invalid enum value. Expected 'MIN' | 'MAX' | 'CENTER' | 'BASELINE', received 'STRETCH'
// CORRECT — use 'MIN' on the parent, then set children to FILL on the cross axis
comp.counterAxisAlignItems = 'MIN'
comp.appendChild(child)
// For vertical layout, stretch width:
child.layoutSizingHorizontal = 'FILL'
// For horizontal layout, stretch height:
child.layoutSizingVertical = 'FILL'
```
## Variable collection mode limits are plan-dependent
```js
// Figma limits modes per collection based on the team/org plan:
// Free: 1 mode only (no addMode)
// Professional: up to 4 modes
// Organization/Enterprise: up to 40+ modes
//
// WRONG — creating 20 modes on a Professional plan will fail silently or throw
const coll = figma.variables.createVariableCollection("Variants")
for (let i = 0; i < 20; i++) coll.addMode("mode" + i) // May fail!
// CORRECT — if you need many modes, split across multiple collections
// E.g., instead of 1 collection with 20 modes (variant×color):
// Collection A: 4 modes (variant: plain/outlined/soft/solid)
// Collection B: 5 modes (color: neutral/primary/danger/success/warning)
// Then use setExplicitVariableModeForCollection for BOTH on each component
```
## Variables default to `ALL_SCOPES` — always set scopes explicitly
```js
// WRONG — variable appears in every property picker (fills, text, strokes, spacing, etc.)
const bgColor = figma.variables.createVariable("Background/Default", coll, "COLOR")
// bgColor.scopes defaults to ["ALL_SCOPES"] — pollutes all dropdowns
// CORRECT — restrict to relevant property pickers
const bgColor = figma.variables.createVariable("Background/Default", coll, "COLOR")
bgColor.scopes = ["FRAME_FILL", "SHAPE_FILL"] // fill pickers only
const textColor = figma.variables.createVariable("Text/Default", coll, "COLOR")
textColor.scopes = ["TEXT_FILL"] // text color picker only
const borderColor = figma.variables.createVariable("Border/Default", coll, "COLOR")
borderColor.scopes = ["STROKE_COLOR"] // stroke picker only
const spacing = figma.variables.createVariable("Space/400", coll, "FLOAT")
spacing.scopes = ["GAP"] // gap/spacing pickers only
// Hide primitives that are only referenced via aliases
const primitive = figma.variables.createVariable("Brand/500", coll, "COLOR")
primitive.scopes = [] // hidden from all pickers
```
## Binding fills on nodes with empty fills
```js
// WRONG — binding to a node with no fills does nothing
const comp = figma.createComponent()
comp.fills = [] // transparent
// Can't bind a color variable to fills that don't exist
// CORRECT — add a placeholder SOLID fill, then bind the variable
const comp = figma.createComponent()
const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }
const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", colorVar)
comp.fills = [boundPaint]
// The variable's resolved value (which may be transparent) will control the actual color
```
## Mode names must be descriptive — never leave 'Mode 1'
Every new `VariableCollection` starts with one mode named `'Mode 1'`. Always rename it immediately. For single-mode collections use `'Default'`; for multi-mode collections use names from the source (e.g. `'Light'`/`'Dark'`, `'Desktop'`/`'Tablet'`/`'Mobile'`).
// WRONG — generic names give no semantic meaning
const coll = figma.variables.createVariableCollection('Colors')
// coll.modes[0].name === 'Mode 1' — left as-is
const darkId = coll.addMode('Mode 2')
// CORRECT — rename immediately to match the source
const coll = figma.variables.createVariableCollection('Colors')
coll.renameMode(coll.modes[0].modeId, 'Light') // was 'Mode 1'
const darkId = coll.addMode('Dark')
// For single-mode collections (primitives, spacing, etc.)
const spacing = figma.variables.createVariableCollection('Spacing')
spacing.renameMode(spacing.modes[0].modeId, 'Default') // was 'Mode 1'
## CSS variable names must not contain spaces
When constructing a `var(--name)` string from a Figma variable name, replace BOTH slashes AND spaces with hyphens and convert to lowercase.
// WRONG — only replacing slashes leaves spaces like 'var(--color-bg-brand secondary hover)'
v.setVariableCodeSyntax('WEB', `var(--${figmaName.replace(/\//g, '-').toLowerCase()})`)
// CORRECT — replace all whitespace and slashes in one pass
v.setVariableCodeSyntax('WEB', `var(--${figmaName.replace(/[\s\/]+/g, '-').toLowerCase()})`)
**Best practice**: Preserve the original CSS variable name from the source token file rather than deriving it from the Figma name.
// Preferred — use the source CSS name directly
v.setVariableCodeSyntax('WEB', `var(${token.cssVar})`) // e.g. '--color-bg-brand-secondary-hover'
## `detachInstance()` invalidates ancestor node IDs
When `detachInstance()` is called on a nested instance inside a library component instance, the parent instance may also get implicitly detached (converted from INSTANCE to FRAME with a **new ID**). Any previously cached ID for the parent becomes invalid.
```js
// WRONG — using cached parent ID after child detach
const parentId = parentInstance.id;
nestedChild.detachInstance();
const parent = await figma.getNodeByIdAsync(parentId); // null! ID changed.
// CORRECT — re-discover by traversal from a stable (non-instance) frame
const stableFrame = await figma.getNodeByIdAsync(manualFrameId);
nestedChild.detachInstance();
const parent = stableFrame.findOne(n => n.name === "ParentName");
```
If detaching multiple nested instances across siblings, do it in a **single** `use_figma` call — discover all targets by traversal before any detachment mutates the tree.
@@ -0,0 +1,516 @@
# Plugin API Patterns
> Part of the [use_figma skill](../SKILL.md). Quick reference for common Figma Plugin API operations.
## Contents
- Execution Basics
- Creating Nodes
- Fills and Strokes
- Auto Layout
- Effects
- Opacity and Blend Modes
- Corner Radius and Clipping
- Grouping and Organization
- Components and Variants
- Styles
- Cloning, Finding Nodes, and Grids
- Constraints and Viewport
## Execution Basics
### Page Context
Page context resets between `use_figma` calls — `figma.currentPage` always starts on the first page. Use `await figma.setCurrentPageAsync(page)` at the start of each invocation to switch to the correct page.
```javascript
const targetPage = figma.root.children.find(p => p.name === "My Page");
await figma.setCurrentPageAsync(targetPage);
// targetPage.children is now populated
```
### Returning Results
Scripts are automatically wrapped in an async IIFE with error handling. Just write plain JS and use `return` to send data back to the agent:
```javascript
// Return an object — auto-serialized to JSON
return { nodeId: frame.id, count: 5 }
// Return a string
return "Created 3 components"
```
Errors are automatically captured — no try/catch needed. `figma.notify()` does **not** exist. Return all information via the `return` value.
### Working Incrementally
Don't build an entire screen in one call. Break work into small steps:
1. Create tokens/variables
2. Create text styles
3. Build individual components
4. Compose sections
5. Assemble screens
Verify structure with `get_metadata` between steps. Use `get_screenshot` after each major creation milestone to catch visual problems early.
## Creating Nodes
### Frames
```javascript
const frame = figma.createFrame();
frame.name = "Container";
frame.resize(1440, 900);
frame.x = 0;
frame.y = 0;
frame.fills = [{ type: "SOLID", color: { r: 0.98, g: 0.98, b: 0.99 } }];
```
### Text
```javascript
// MUST load font before any text operations
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
const text = figma.createText();
text.fontName = { family: "Inter", style: "Regular" };
text.fontSize = 16;
text.lineHeight = { value: 24, unit: "PIXELS" };
text.letterSpacing = { value: 0, unit: "PERCENT" };
text.characters = "Hello World";
text.fills = [{ type: "SOLID", color: { r: 0.1, g: 0.1, b: 0.12 } }];
```
### Rectangles
```javascript
const rect = figma.createRectangle();
rect.name = "Background";
rect.resize(400, 300);
rect.cornerRadius = 12;
rect.fills = [{ type: "SOLID", color: { r: 0.95, g: 0.95, b: 0.96 } }];
```
### Ellipses
```javascript
const circle = figma.createEllipse();
circle.name = "Avatar Circle";
circle.resize(48, 48);
circle.fills = [{ type: "SOLID", color: { r: 0.85, g: 0.87, b: 0.90 } }];
```
### Lines
```javascript
const line = figma.createLine();
line.name = "Divider";
line.resize(400, 0);
line.strokes = [{ type: "SOLID", color: { r: 0, g: 0, b: 0 }, opacity: 0.08 }];
line.strokeWeight = 1;
```
### SVG Import
```javascript
const svgString = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 12h14M12 5l7 7-7 7" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
const node = figma.createNodeFromSvg(svgString);
node.name = "Icon/Arrow Right";
node.resize(24, 24);
```
## Fills & Strokes
### Solid Fill
```javascript
node.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.2, b: 0.25 } }];
```
### Fill with Opacity
```javascript
node.fills = [{ type: "SOLID", color: { r: 0.2, g: 0.2, b: 0.25 }, opacity: 0.5 }];
```
### No Fill (Transparent)
```javascript
node.fills = [];
```
### Linear Gradient
```javascript
node.fills = [{
type: "GRADIENT_LINEAR",
gradientStops: [
{ color: { r: 0.2, g: 0.36, b: 0.96, a: 1 }, position: 0 },
{ color: { r: 0.56, g: 0.24, b: 0.88, a: 1 }, position: 1 }
],
gradientTransform: [[1, 0, 0], [0, 1, 0]]
}];
```
### Strokes
```javascript
node.strokes = [{ type: "SOLID", color: { r: 0.85, g: 0.85, b: 0.87 } }];
node.strokeWeight = 1;
node.strokeAlign = "INSIDE"; // "CENTER", "OUTSIDE"
```
### Multiple Fills (Layered)
```javascript
node.fills = [
{ type: "SOLID", color: { r: 0.95, g: 0.95, b: 0.96 } },
{ type: "SOLID", color: { r: 0.2, g: 0.36, b: 0.96 }, opacity: 0.05 }
];
```
## Auto Layout
### Setting Up Auto Layout
```javascript
const frame = figma.createFrame();
frame.layoutMode = "VERTICAL"; // or "HORIZONTAL"
frame.primaryAxisSizingMode = "AUTO"; // Hug main axis
frame.counterAxisSizingMode = "FIXED"; // Fixed cross axis
frame.resize(360, 1); // Width fixed, height auto
frame.itemSpacing = 16; // Gap between children
frame.paddingTop = 24;
frame.paddingBottom = 24;
frame.paddingLeft = 24;
frame.paddingRight = 24;
```
### Alignment
```javascript
// Main axis (direction of layout)
frame.primaryAxisAlignItems = "MIN"; // Start
frame.primaryAxisAlignItems = "CENTER"; // Center
frame.primaryAxisAlignItems = "MAX"; // End
frame.primaryAxisAlignItems = "SPACE_BETWEEN"; // Distribute
// Cross axis
frame.counterAxisAlignItems = "MIN"; // Start
frame.counterAxisAlignItems = "CENTER"; // Center
frame.counterAxisAlignItems = "MAX"; // End
// NOTE: 'STRETCH' is NOT valid — use 'MIN' + child.layoutSizingX = 'FILL'
```
### Child Sizing
```javascript
// IMPORTANT: FILL can only be set AFTER the child is appended to an auto-layout parent
parent.appendChild(child)
child.layoutSizingHorizontal = "FILL"; // Stretch to parent
child.layoutSizingHorizontal = "HUG"; // Shrink to content
child.layoutSizingHorizontal = "FIXED"; // Manual width
child.layoutSizingVertical = "FILL";
child.layoutSizingVertical = "HUG";
child.layoutSizingVertical = "FIXED";
```
### Wrapping (Grid-like Layout)
```javascript
frame.layoutMode = "HORIZONTAL";
frame.layoutWrap = "WRAP";
frame.itemSpacing = 24; // Horizontal gap
frame.counterAxisSpacing = 24; // Vertical gap (between rows)
```
### Absolute Positioning Within Auto Layout
```javascript
child.layoutPositioning = "ABSOLUTE";
child.constraints = { horizontal: "MAX", vertical: "MIN" }; // Top-right
child.x = parentWidth - childWidth - 8;
child.y = 8;
```
## Effects
### Drop Shadow
```javascript
node.effects = [{
type: "DROP_SHADOW",
color: { r: 0, g: 0, b: 0, a: 0.08 },
offset: { x: 0, y: 4 },
radius: 16,
spread: -2,
visible: true,
blendMode: "NORMAL"
}];
```
### Inner Shadow
```javascript
node.effects = [{
type: "INNER_SHADOW",
color: { r: 0, g: 0, b: 0, a: 0.05 },
offset: { x: 0, y: 1 },
radius: 2,
spread: 0,
visible: true,
blendMode: "NORMAL"
}];
```
### Background Blur
```javascript
node.effects = [{
type: "BACKGROUND_BLUR",
radius: 16,
visible: true
}];
```
### Layer Blur
```javascript
node.effects = [{
type: "LAYER_BLUR",
radius: 8,
visible: true
}];
```
### Multiple Effects
```javascript
node.effects = [
{ type: "DROP_SHADOW", color: { r: 0, g: 0, b: 0, a: 0.04 }, offset: { x: 0, y: 1 }, radius: 3, spread: 0, visible: true, blendMode: "NORMAL" },
{ type: "DROP_SHADOW", color: { r: 0, g: 0, b: 0, a: 0.06 }, offset: { x: 0, y: 8 }, radius: 24, spread: -4, visible: true, blendMode: "NORMAL" }
];
```
## Opacity & Blend Modes
```javascript
node.opacity = 0.5;
node.blendMode = "NORMAL"; // "MULTIPLY", "SCREEN", "OVERLAY", "DARKEN", "LIGHTEN", etc.
```
## Corner Radius
```javascript
// Uniform
node.cornerRadius = 12;
// Per-corner
node.topLeftRadius = 12;
node.topRightRadius = 12;
node.bottomLeftRadius = 0;
node.bottomRightRadius = 0;
```
## Clipping
```javascript
frame.clipsContent = true; // Children clipped to frame bounds
```
## Grouping & Organization
### Groups
```javascript
const group = figma.group([node1, node2, node3], figma.currentPage);
group.name = "Grouped Elements";
```
### Sections
```javascript
const section = figma.createSection();
section.name = "My Section";
section.resizeWithoutConstraints(800, 600);
section.x = 0;
section.y = 0;
// IMPORTANT: Sections don't auto-resize — always resize after adding content
```
### Appending Children
```javascript
parentFrame.appendChild(childNode);
// Insert at a specific index
parentFrame.insertChild(0, childNode); // Insert at beginning
```
## Components & Variants
### Create Component
```javascript
const component = figma.createComponent();
component.name = "Button/Primary";
component.description = "Primary action button.";
```
### Create Instance
```javascript
const instance = component.createInstance();
instance.x = 200;
instance.y = 100;
```
### Import Components by Key (Team Libraries)
These methods import components from **team libraries** (not the same file). For components in the current file, use `figma.getNodeByIdAsync()` or `findOne()`/`findAll()`.
```javascript
// Import a published component from a team library by its key
const comp = await figma.importComponentByKeyAsync(componentKey)
const instance = comp.createInstance()
// Import a published component set from a team library by its key
const set = await figma.importComponentSetByKeyAsync(componentSetKey)
const variant = set.defaultVariant
const variantInstance = variant.createInstance()
```
### Combine as Variants
```javascript
// IMPORTANT: Pass ComponentNodes (not frames)
const componentSet = figma.combineAsVariants(
[variantA, variantB, variantC],
figma.currentPage
);
componentSet.name = "Button";
componentSet.description = "Button component with multiple variants.";
// CRITICAL: Layout variants in a grid after combining (they stack at 0,0)
let maxX = 0, maxY = 0;
componentSet.children.forEach((child, i) => {
child.x = (i % numCols) * colWidth;
child.y = Math.floor(i / numCols) * rowHeight;
});
for (const child of componentSet.children) {
maxX = Math.max(maxX, child.x + child.width);
maxY = Math.max(maxY, child.y + child.height);
}
componentSet.resizeWithoutConstraints(maxX + 40, maxY + 40);
```
### Component Properties
```javascript
// addComponentProperty returns a STRING key — capture it!
const labelKey = component.addComponentProperty("label", "TEXT", "Button");
const showIconKey = component.addComponentProperty("showIcon", "BOOLEAN", true);
const iconSlotKey = component.addComponentProperty("iconSlot", "INSTANCE_SWAP", defaultIconId);
// MUST link properties to child nodes via componentPropertyReferences
labelNode.componentPropertyReferences = { characters: labelKey };
iconInstance.componentPropertyReferences = {
visible: showIconKey,
mainComponent: iconSlotKey
};
```
## Styles
### Text Style
```javascript
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
const style = figma.createTextStyle();
style.name = "Body/Default";
style.fontName = { family: "Inter", style: "Regular" };
style.fontSize = 16;
style.lineHeight = { value: 24, unit: "PIXELS" };
style.letterSpacing = { value: 0, unit: "PERCENT" };
// Apply to a text node
textNode.textStyleId = style.id;
```
### Effect Style
```javascript
const shadowStyle = figma.createEffectStyle();
shadowStyle.name = "Shadow/Subtle";
shadowStyle.effects = [{
type: "DROP_SHADOW",
color: { r: 0, g: 0, b: 0, a: 0.06 },
offset: { x: 0, y: 2 },
radius: 8,
spread: 0,
visible: true,
blendMode: "NORMAL"
}];
// Apply to a node
frame.effectStyleId = shadowStyle.id;
```
## Cloning & Duplication
```javascript
const clone = originalNode.clone();
clone.x = originalNode.x + originalNode.width + 40;
clone.name = "Copy of " + originalNode.name;
```
## Finding Nodes
```javascript
// Find by name on current page
const node = figma.currentPage.findOne(n => n.name === "My Frame");
// Find all by type
const allTexts = figma.currentPage.findAll(n => n.type === "TEXT");
// Find all by name pattern
const allButtons = figma.currentPage.findAll(n => n.name.startsWith("Button/"));
```
## Layout Grids
```javascript
frame.layoutGrids = [
{
pattern: "COLUMNS",
alignment: "STRETCH",
count: 12,
gutterSize: 24,
offset: 80,
visible: true
}
];
```
## Constraints (Non-Auto-Layout Frames)
```javascript
child.constraints = {
horizontal: "LEFT_RIGHT", // LEFT, RIGHT, CENTER, LEFT_RIGHT, SCALE
vertical: "TOP" // TOP, BOTTOM, CENTER, TOP_BOTTOM, SCALE
};
```
## Viewport & Zoom
```javascript
// Zoom to fit specific nodes
figma.viewport.scrollAndZoomIntoView([frame1, frame2]);
```
File diff suppressed because one or more lines are too long
@@ -0,0 +1,437 @@
# Plugin API Index
> Full typings: `plugin-api-standalone.d.ts` (11,292 lines)
> Grep by symbol name to jump to definition. All `L#` line numbers refer to that file.
---
## figma.\* — PluginAPI (L24)
### Identity & State
| Member | Type |
| ------------------------------- | -------------------------------------------------------------------------------- |
| `apiVersion` | `'1.0.0'` |
| `editorType` | `'figma' \| 'figjam' \| 'dev' \| 'slides' \| 'buzz'` |
| `mode` | `'default' \| 'textreview' \| 'inspect' \| 'codegen' \| 'linkpreview' \| 'auth'` |
| `fileKey` | `string \| undefined` |
| `root` | `DocumentNode` |
| `currentPage` | `PageNode` — assign via `setCurrentPageAsync` |
| `currentUser` | `User \| null` |
| `mixed` | `unique symbol` — sentinel for mixed values in selection |
| `skipInvisibleInstanceChildren` | `boolean` |
### Navigation & Lookup
| Method | Returns |
| --------------------------- | ------------------------------------------------------- |
| `setCurrentPageAsync(page)` | `Promise<void>`**MUST use this**; sync setter throws |
| `getNodeByIdAsync(id)` | `Promise<BaseNode \| null>` |
| `getNodeById(id)` | `BaseNode \| null` |
| `getStyleByIdAsync(id)` | `Promise<BaseStyle \| null>` |
| `getStyleById(id)` | `BaseStyle \| null` |
### Create Nodes
| Method | Returns |
| ----------------------------------- | --------------------------- |
| `createFrame()` | `FrameNode` |
| `createComponent()` | `ComponentNode` |
| `createComponentFromNode(node)` | `ComponentNode` |
| `createRectangle()` | `RectangleNode` |
| `createEllipse()` | `EllipseNode` |
| `createLine()` | `LineNode` |
| `createPolygon()` | `PolygonNode` |
| `createStar()` | `StarNode` |
| `createVector()` | `VectorNode` |
| `createText()` | `TextNode` |
| `createSection()` | `SectionNode` |
| `createPage()` | `PageNode` |
| `createSlice()` | `SliceNode` |
| `createBooleanOperation()` | `BooleanOperationNode` |
| `createTable(rows?, cols?)` | `TableNode` |
| `createImage(data: Uint8Array)` | `Image` |
| `createNodeFromSvg(svg)` | `FrameNode` |
| `createNodeFromJSXAsync(jsx)` | `Promise<SceneNode>` |
| `importComponentByKeyAsync(key)` | `Promise<ComponentNode>` |
| `importComponentSetByKeyAsync(key)` | `Promise<ComponentSetNode>` |
| `importStyleByKeyAsync(key)` | `Promise<BaseStyle>` |
### Styles (Local)
| Method | Returns |
| ---------------------------------- | --------------- |
| `createPaintStyle()` | `PaintStyle` |
| `createTextStyle()` | `TextStyle` |
| `createEffectStyle()` | `EffectStyle` |
| `createGridStyle()` | `GridStyle` |
| `getLocalPaintStyles()` / `Async` | `PaintStyle[]` |
| `getLocalTextStyles()` / `Async` | `TextStyle[]` |
| `getLocalEffectStyles()` / `Async` | `EffectStyle[]` |
| `getLocalGridStyles()` / `Async` | `GridStyle[]` |
### Fonts
| Method | Notes |
| --------------------------- | ---------------------------------- |
| `loadFontAsync(fontName)` | **MUST call before any text edit** |
| `listAvailableFontsAsync()` | `Promise<Font[]>` |
| `hasMissingFont` | `boolean` |
### Plugin Lifecycle
| Method | Notes |
| --------------------------------------- | ------------------------------------------------------------ |
| `closePlugin(message?)` | Auto-called; use `return` instead to pass results back |
| `closePluginWithFailure(message?)` | Auto-called on errors; do not call manually |
| `commitUndo()` | Snapshot to undo history |
| `triggerUndo()` | Revert to last snapshot |
| `saveVersionHistoryAsync(title, desc?)` | `Promise<VersionHistoryResult>` |
| `notify(message, options?)` | **throws "not implemented" in use_figma — do not use** |
| `openExternal(url)` | Opens URL in browser |
### Sub-APIs (properties on figma)
| Property | Interface | L# |
| --------------------- | ------------------------ | ----- |
| `figma.variables` | `VariablesAPI` | L2016 |
| `figma.ui` | `UIAPI` | L2604 |
| `figma.util` | `UtilAPI` | L2691 |
| `figma.constants` | `ConstantsAPI` | L2809 |
| `figma.clientStorage` | `ClientStorageAPI` | L2531 |
| `figma.viewport` | `ViewportAPI` | L3086 |
| `figma.parameters` | `ParametersAPI` | L3292 |
| `figma.teamLibrary` | `TeamLibraryAPI` | L2372 |
| `figma.annotations` | `AnnotationsAPI` | L2187 |
| `figma.codegen` | `CodegenAPI` | L2871 |
| `figma.textreview?` | `TextReviewAPI` | L3166 |
| `figma.payments?` | `PaymentsAPI` | L2420 |
| `figma.buzz` | `BuzzAPI` | L2211 |
| `figma.timer?` | `TimerAPI` (FigJam only) | L3053 |
---
## VariablesAPI — figma.variables (L2016)
```
getVariableByIdAsync(id) Promise<Variable | null> ← preferred; sync deprecated
getVariableCollectionByIdAsync(id) Promise<VariableCollection | null> ← preferred; sync deprecated
getLocalVariablesAsync(type?) Promise<Variable[]> ← preferred; filter by VariableResolvedDataType; sync deprecated
getLocalVariableCollectionsAsync() Promise<VariableCollection[]> ← preferred; sync deprecated
createVariable(name, collection, type) Variable
createVariableCollection(name) VariableCollection
createVariableAlias(variable) VariableAlias
importVariableByKeyAsync(key) Promise<Variable>
setBoundVariableForPaint(paint, field, variable) → returns NEW paint — reassign
setBoundVariableForEffect(effect, field, variable) → returns NEW effect — reassign
setBoundVariableForLayoutGrid(grid, field, variable)
```
**Variable (L10204):** `name`, `resolvedType`, `codeSyntax`, `scopes`, `hiddenFromPublishing`, `valuesByMode`, `variableCollectionId`
- `setVariableCodeSyntax(platform, value)` — platform: `'WEB' | 'ANDROID' | 'iOS'`
- `setValueForMode(collectionId, modeId, value)`
- `remove()`
**VariableCollection (L10418):** `name`, `modes`, `variableIds`, `defaultModeId`, `hiddenFromPublishing`
- `addMode(name)``modeId`; `removeMode(modeId)`; `renameMode(modeId, name)`
---
## Node Types
### Concrete Scene Nodes
| Node | L# | Key characteristics |
| ---------------------- | ------ | -------------------------------------------------- |
| `DocumentNode` | L8960 | Root; `children: PageNode[]` |
| `PageNode` | L9119 | `children`, local styles, `backgrounds` |
| `FrameNode` | L9311 | `DefaultFrameMixin` — auto-layout, clips, children |
| `GroupNode` | L9321 | Children only, no auto-layout |
| `ComponentNode` | L9678 | Like Frame + publishable |
| `ComponentSetNode` | L9653 | Variant set container |
| `InstanceNode` | L9719 | Like Frame; `mainComponent`, `detach()` |
| `RectangleNode` | L9378 | `DefaultShapeMixin` + corners |
| `EllipseNode` | L9410 | + `arcData` |
| `LineNode` | L9396 | |
| `PolygonNode` | L9430 | |
| `StarNode` | L9450 | |
| `VectorNode` | L9476 | Vector paths |
| `TextNode` | L9493 | Rich text, fonts, segments |
| `TextPathNode` | L9564 | Text along path |
| `BooleanOperationNode` | L9792 | `booleanOperation` property |
| `SliceNode` | L9368 | Export only |
| `SectionNode` | L10754 | Grouping + fills |
| `TableNode` | L9862 | `TableCellNode` children |
**FigJam only:** `StickyNode` L9812, `ConnectorNode` L10121, `ShapeWithTextNode` L9999, `StampNode` L9838, `CodeBlockNode` L10080, `EmbedNode` L10661, `LinkUnfurlNode` L10701, `MediaNode` L10721
**Slides only:** `SlideNode` L10784, `SlideRowNode` L10809, `SlideGridNode` L10822
**Union types:**
```
type SceneNode (L10917) = FrameNode | GroupNode | SliceNode | RectangleNode | LineNode
| EllipseNode | PolygonNode | StarNode | VectorNode | TextNode | ComponentSetNode
| ComponentNode | InstanceNode | BooleanOperationNode | SectionNode | ...
type BaseNode (L10913) = DocumentNode | PageNode | SceneNode
```
---
## Mixin Interfaces
| Mixin | L# | Provides |
| ---------------------------- | ----- | ----------------------------------------------------------------------------------------------- |
| `BaseNodeMixin` | L5284 | `id`, `name`, `type`, `parent`, `remove()`, plugin data |
| `SceneNodeMixin` | L5561 | `visible`, `locked`, `opacity`, variable bindings |
| `ChildrenMixin` | L5773 | `children`, `appendChild()`, `insertChild()`, `findAll()`, `findOne()`, `findAllWithCriteria()` |
| `LayoutMixin` | L6135 | `x`, `y`, `width`, `height`, `rotation`, `resize()`, `rescale()` |
| `AutoLayoutMixin` | L6436 | `layoutMode`, axis alignment, padding, `itemSpacing`, `layoutSizingHorizontal/Vertical` |
| `AutoLayoutChildrenMixin` | L7064 | `layoutAlign`, `layoutGrow`, sizing — **set AFTER `appendChild()`** |
| `GridLayoutMixin` | L6939 | CSS Grid tracks, gap, template |
| `GridChildrenMixin` | L7127 | grid child positioning |
| `GeometryMixin` | L7485 | `fills`, `strokes`, `strokeWeight`, `strokeAlign` |
| `MinimalFillsMixin` | L7328 | `fills` only |
| `MinimalStrokesMixin` | L7246 | `strokes`, `strokeWeight` |
| `BlendMixin` | L6339 | `opacity`, `blendMode`, `isMask`, `effects` |
| `CornerMixin` | L7537 | `cornerRadius`, `cornerSmoothing` |
| `RectangleCornerMixin` | L7560 | Per-corner radii |
| `ExportMixin` | L7577 | `exportSettings`, `exportAsync()` |
| `ReactionMixin` | L7704 | `reactions` (prototyping) |
| `PublishableMixin` | L7875 | `description`, `key`, `getPublishStatusAsync()` |
| `VariantMixin` | L8182 | `variantProperties` |
| `ComponentPropertiesMixin` | L8229 | `componentProperties`, `addComponentProperty()` |
| `PluginDataMixin` | L5443 | `getSharedPluginData()`, `setSharedPluginData()` supported; `getPluginData()`, `setPluginData()` **NOT supported** |
| `FramePrototypingMixin` | L7651 | `overflowDirection`, `numberOfFixedChildren` |
| `BaseFrameMixin` | L7939 | ChildrenMixin + LayoutMixin + AutoLayoutMixin + GeometryMixin + … |
| `DefaultFrameMixin` | L7997 | BaseFrameMixin + FramePrototypingMixin + ReactionMixin |
| `DefaultShapeMixin` | L7928 | BlendMixin + GeometryMixin + LayoutMixin + ExportMixin + ReactionMixin |
| `ExplicitVariableModesMixin` | L9084 | `setExplicitVariableModeForCollection()` |
---
## Paint & Fill (L4302)
| Type | L# | Notes |
| --------------- | ----- | --------------------------------------------------------------------------------- |
| `SolidPaint` | L4302 | `type:'SOLID'`, `color: RGB`, `opacity`, `visible`, `blendMode` |
| `GradientPaint` | L4357 | `type: 'GRADIENT_LINEAR\|RADIAL\|ANGULAR\|DIAMOND'`, `gradientStops: ColorStop[]` |
| `ImagePaint` | L4377 | `type:'IMAGE'`, `imageHash`, `scaleMode` |
| `VideoPaint` | L4413 | `type:'VIDEO'` |
| `PatternPaint` | L4449 | `type:'PATTERN'` |
| `type Paint` | L4481 | Union of all five |
| `ColorStop` | L4271 | `{ position: number, color: RGBA }` |
| `ImageFilters` | L4290 | exposure, contrast, saturation, etc. |
> **CRITICAL**: Fills/strokes are **read-only arrays** — clone, modify, reassign.
---
## Effects (L3966)
| Type | L# |
| ---------------------------------- | ----- |
| `DropShadowEffect` | L3966 |
| `InnerShadowEffect` | L4009 |
| `BlurEffect` (Normal/Progressive) | L4048 |
| `NoiseEffect` (Mono/Duo/Multitone) | L4105 |
| `TextureEffect` | L4180 |
| `GlassEffect` | L4209 |
| `type Effect` | L4250 |
---
## Typography
| Type | L# | Notes |
| ------------------- | ----- | -------------------------------------------------------------------------------------- |
| `FontName` | L3697 | `{ family: string, style: string }` |
| `TextNode` | L9493 | `characters`, `textAlignHorizontal`, `fontSize`, `fontName`, `getStyledTextSegments()` |
| `StyledTextSegment` | L4882 | Per-range text properties |
| `LetterSpacing` | L4826 | `{ value, unit: 'PIXELS'\|'PERCENT' }` |
| `LineHeight` | L4830 | `{ value, unit } \| { unit: 'AUTO' }` |
| `TextCase` | L3701 | `'ORIGINAL'\|'UPPER'\|'LOWER'\|'TITLE'\|'SMALL_CAPS'` |
| `TextDecoration` | L3702 | `'NONE'\|'UNDERLINE'\|'STRIKETHROUGH'` |
| `OpenTypeFeature` | L3728 | Ligatures, numerals, etc. |
---
## Variables & Bindings
| Type | L# | Notes |
| ----------------------------- | ------ | ------------------------------------------------------------- |
| `Variable` | L10204 | Core variable object |
| `VariableCollection` | L10418 | Collection of variables + modes |
| `VariableAlias` | L10172 | Reference to another variable |
| `VariableValue` | L10176 | `boolean \| string \| number \| RGB \| RGBA \| VariableAlias` |
| `VariableResolvedDataType` | L10171 | `'BOOLEAN' \| 'COLOR' \| 'FLOAT' \| 'STRING'` |
| `VariableDataType` | L5023 | Includes `'VARIABLE_ALIAS' \| 'EXPRESSION'` |
| `VariableScope` | L10177 | Where variable can be applied |
| `CodeSyntaxPlatform` | L10203 | `'WEB' \| 'ANDROID' \| 'iOS'` |
| `VariableBindableNodeField` | L5712 | Node fields that accept variable binding |
| `VariableBindableTextField` | L5739 | Text-specific bindable fields |
| `VariableBindablePaintField` | L5748 | `'color'` |
| `VariableBindableEffectField` | L5751 | `'color'\|'radius'\|'spread'\|'offsetX'\|'offsetY'` |
---
## Styles
| Interface | L# | Notes |
| ---------------- | ------ | ------------------------------------------------------ |
| `BaseStyleMixin` | L10977 | `name`, `id`, `key`, `type`, `description`, `remove()` |
| `PaintStyle` | L11002 | `type:'PAINT'`, `paints: Paint[]` |
| `TextStyle` | L11018 | `type:'TEXT'`, font properties |
| `EffectStyle` | L11087 | `type:'EFFECT'`, `effects: Effect[]` |
| `GridStyle` | L11103 | `type:'GRID'`, `layoutGrids` |
| `type BaseStyle` | L11119 | Union of all four |
| `type StyleType` | L10955 | `'PAINT' \| 'TEXT' \| 'EFFECT' \| 'GRID'` |
---
## Primitives & Geometry
| Type | L# | Shape |
| ---------------- | ----- | --------------------------------------------- |
| `Vector` | L3667 | `{ x: number, y: number }` |
| `Rect` | L3671 | `{ x, y, width, height }` |
| `RGB` | L3680 | `{ r, g, b }`**01 range, not 0255** |
| `RGBA` | L3688 | `{ r, g, b, a }`**01 range** |
| `Transform` | L3666 | `[[a,b,tx],[c,d,ty]]` 2×3 affine matrix |
| `ArcData` | L3958 | `{ startingAngle, endingAngle, innerRadius }` |
| `Constraints` | L4264 | `{ horizontal, vertical }: ConstraintType` |
| `ConstraintType` | L4260 | `'MIN'\|'CENTER'\|'MAX'\|'STRETCH'\|'SCALE'` |
| `VectorPath` | L4792 | `{ windingRule, data: string }` |
| `VectorNetwork` | L4775 | vertices + segments + regions |
| `Guide` | L4482 | `{ axis, offset }` |
---
## Prototyping
| Type | L# | Notes |
| --------------------- | ----- | --------------------------------------------------------- |
| `Reaction` | L5015 | trigger + action pair |
| `Trigger` | L5146 | what initiates the reaction |
| `Action` | L5064 | what happens |
| `Transition` | L5145 | `SimpleTransition \| DirectionalTransition` |
| `Easing` | L5182 | easing curve definition |
| `Navigation` | L5178 | `'NAVIGATE'\|'SWAP'\|'OVERLAY'\|'SCROLL_TO'\|'CHANGE_TO'` |
| `OverflowDirection` | L5215 | `'NONE'\|'HORIZONTAL'\|'VERTICAL'\|'BOTH'` |
| `OverlayPositionType` | L5219 | overlay placement |
---
## Events & Changes
| Type | L# | Notes |
| --------------------- | ----- | --------------------------------------------------------------- |
| `ArgFreeEventType` | L11 | `'selectionchange'\|'currentpagechange'\|'close'\|timer events` |
| `RunEvent` | L3321 | plugin run with parameters |
| `DropEvent` | L3339 | drag-and-drop |
| `DocumentChangeEvent` | L3359 | any document change |
| `NodeChangeEvent` | L3626 | node property changes |
| `NodeChangeProperty` | L3499 | all watchable property names |
| `StyleChangeEvent` | L3365 | style create/delete/update |
| `DocumentChange` | L3489 | `CreateChange \| DeleteChange \| PropertyChange` |
| `TextReviewEvent` | L3657 | text review mode |
---
## Export
| Type | L# | Notes |
| --------------------------- | ----- | --------------------------------------------- |
| `ExportSettingsImage` | L4561 | PNG/JPG/WEBP/BMP |
| `ExportSettingsSVG` | L4634 | |
| `ExportSettingsPDF` | L4653 | |
| `ExportSettingsREST` | L4667 | |
| `ExportSettingsConstraints` | L4554 | `{ type: 'SCALE'\|'WIDTH'\|'HEIGHT', value }` |
---
## Key Sub-API Surfaces
**ClientStorageAPI (L2531):** `getAsync(key)`, `setAsync(key, value)`, `keysAsync()`, `deleteAsync(key)`
**ViewportAPI (L3086):** `center: Vector`, `zoom: number`, `scrollAndZoomIntoView(nodes)`, `bounds: Rect`
**UtilAPI (L2691):** `solidPaint(hex, opacity?)`, `rgba(r,g,b,a?)`, `rgb(r,g,b)`, `colorToHex(color)`, `loadImageAsync(url)`, `clone(val)`
**TeamLibraryAPI (L2372):** `getAvailableLibraryVariableCollectionsAsync()`, `importVariableByKeyAsync(key)`
**Image (L11120):** `hash`, `getBytesAsync()`, `getSizeAsync()`
---
## All Symbols (flat — grep these against the .d.ts file)
To find any symbol: `grep -n "^interface Foo\|^type Foo\|^declare type Foo" plugin-api-standalone.d.ts`
```
PluginAPI VariablesAPI AnnotationsAPI TeamLibraryAPI
UIAPI UtilAPI ViewportAPI ClientStorageAPI
ConstantsAPI CodegenAPI PaymentsAPI TextReviewAPI
ParametersAPI TimerAPI BuzzAPI DevResourcesAPI
DocumentNode PageNode FrameNode GroupNode
ComponentNode ComponentSetNode InstanceNode RectangleNode
EllipseNode LineNode PolygonNode StarNode
VectorNode TextNode TextPathNode BooleanOperationNode
SliceNode SectionNode TableNode TableCellNode
StickyNode ConnectorNode ShapeWithTextNode StampNode
CodeBlockNode EmbedNode LinkUnfurlNode MediaNode
WidgetNode SlideNode SlideRowNode SlideGridNode
TransformGroupNode HighlightNode WashiTapeNode
BaseNodeMixin SceneNodeMixin ChildrenMixin LayoutMixin
AutoLayoutMixin AutoLayoutChildrenMixin GridLayoutMixin GridChildrenMixin
GeometryMixin MinimalFillsMixin MinimalStrokesMixin BlendMixin
MinimalBlendMixin CornerMixin RectangleCornerMixin ExportMixin
ReactionMixin PublishableMixin VariantMixin ComponentPropertiesMixin
PluginDataMixin DevResourcesMixin DevStatusMixin StickableMixin
ConstraintMixin DimensionAndPositionMixin AspectRatioLockMixin FramePrototypingMixin
BaseFrameMixin DefaultFrameMixin DefaultShapeMixin OpaqueNodeMixin
VectorLikeMixin ComplexStrokesMixin IndividualStrokesMixin ContainerMixin
AnnotationsMixin MeasurementsMixin ExplicitVariableModesMixin
Variable VariableCollection VariableAlias ExtendedVariableCollection
LibraryVariableCollection LibraryVariable
VariableValue VariableResolvedDataType VariableDataType VariableScope
CodeSyntaxPlatform VariableBindableNodeField VariableBindableTextField
VariableBindablePaintField VariableBindableEffectField VariableBindableLayoutGridField
SolidPaint GradientPaint ImagePaint VideoPaint
PatternPaint Paint ColorStop ImageFilters
DropShadowEffect InnerShadowEffect BlurEffect NoiseEffect
TextureEffect GlassEffect Effect
LayoutGrid RowsColsLayoutGrid GridLayoutGrid
PaintStyle TextStyle EffectStyle GridStyle
BaseStyle BaseStyleMixin StyleType
FontName Font LetterSpacing LineHeight
TextCase TextDecoration TextDecorationStyle FontStyle
OpenTypeFeature StyledTextSegment LeadingTrim
Vector Rect RGB RGBA
Transform ArcData Constraints ConstraintType
VectorPath VectorNetwork VectorVertex VectorSegment
VectorRegion Guide BlendMode MaskType
Reaction Trigger Action Transition
Easing Navigation OverflowDirection OverlayPositionType
OverlayBackground PublishStatus
ArgFreeEventType RunEvent DropEvent DocumentChangeEvent
NodeChangeEvent NodeChangeProperty StyleChangeEvent DocumentChange
TextReviewEvent SlidesViewChangeEvent CanvasViewChangeEvent
ExportSettingsImage ExportSettingsSVG ExportSettingsPDF ExportSettingsREST
ExportSettingsConstraints
User ActiveUser BaseUser Image
Video VersionHistoryResult FindAllCriteria
```
@@ -0,0 +1,205 @@
# Text Style API Patterns
> Part of the [use_figma skill](../SKILL.md). How to create, apply, and inspect text styles using the Plugin API.
>
> For design system context (when to create text styles, how they relate to tokens, headless limitations), see [wwds-text-styles](working-with-design-systems/wwds-text-styles.md).
## Contents
- Listing Text Styles
- Creating a Text Style
- Probing Font Styles
- Creating a Type Ramp (Multi-Step)
- Importing Library Text Styles
- Applying Text Styles to Nodes
## Listing Text Styles
```javascript
/**
* Lists all local text styles with their key properties.
*
* @returns {Promise<Array<{id: string, name: string, key: string, fontSize: number, fontName: FontName, lineHeight: LineHeight, letterSpacing: LetterSpacing}>>}
*/
async function listTextStyles() {
const styles = await figma.getLocalTextStylesAsync();
return styles.map(s => ({
id: s.id,
name: s.name,
key: s.key,
fontSize: s.fontSize,
fontName: s.fontName,
lineHeight: s.lineHeight,
letterSpacing: s.letterSpacing
}));
}
```
Full runnable script:
```javascript
const results = await listTextStyles();
return results;
```
## Creating a Text Style
Font **MUST** be loaded before setting `fontName`. `lineHeight` and `letterSpacing` must be `{value, unit}` objects — bare numbers throw.
```javascript
/**
* Creates a text style with all typographic properties set.
* Font MUST be loaded before calling.
*
* @param {string} name - Slash-delimited name, e.g. "body/base"
* @param {{ family: string, style: string }} fontName
* @param {number} fontSize - In pixels
* @param {{ value: number, unit: 'PIXELS' | 'PERCENT' } | { unit: 'AUTO' }} lineHeight
* @param {{ value: number, unit: 'PIXELS' | 'PERCENT' }} [letterSpacing]
* @param {string} [description] - e.g. the CSS variable name "CSS: var(--font-body-base)"
* @returns {TextStyle}
*/
function createTextStyleFull(name, fontName, fontSize, lineHeight, letterSpacing, description) {
const style = figma.createTextStyle();
style.name = name;
style.fontName = fontName;
style.fontSize = fontSize;
style.lineHeight = lineHeight; // { unit: 'AUTO' } | { value, unit: 'PIXELS'|'PERCENT' }
if (letterSpacing) style.letterSpacing = letterSpacing;
if (description) style.description = description;
return style;
}
```
## Probing Font Styles
Font style names vary per provider and per file (`"SemiBold"` vs `"Semi Bold"`). Always probe before hardcoding:
```javascript
/**
* Probes available font styles for a given family.
* Useful when font style names are unknown (e.g. "SemiBold" vs "Semi Bold").
*
* @param {string} family - Font family name, e.g. "Inter"
* @param {string[]} stylesToTest - Candidate style names to probe
* @returns {Promise<string[]>} - Style names that loaded successfully
*/
async function probeAvailableFontStyles(family, stylesToTest) {
const available = [];
for (const style of stylesToTest) {
try {
await figma.loadFontAsync({ family, style });
available.push(style);
} catch (_) {}
}
return available;
}
```
## Creating a Type Ramp (Multi-Step)
Handles font loading, deduplication, and idempotency. Each entry: `[name, fontFamily, fontStyle, fontSize_px, lineHeight, cssVar]`.
**HEADLESS NOTE:** `setBoundVariable` on `TextStyle` is not supported in `use_figma`. This function sets raw values. To bind variables, do it interactively in Figma after creation.
```javascript
/**
* Creates a full type ramp from a token definition array.
* Handles font loading, deduplication, and idempotency.
*
* Each entry: [name, fontFamily, fontStyle, fontSize_px, lineHeight, cssVar]
* - lineHeight: { unit: 'AUTO' } or { value: number, unit: 'PIXELS' | 'PERCENT' }
*
* @param {Array} defs - Array of [name, fontFamily, fontStyle, fontSize, lineHeight, cssVar] tuples
* @returns {Promise<{ created: string[], skipped: string[] }>}
*/
async function createTypeRamp(defs) {
const uniqueFonts = new Set();
for (const [, family, style] of defs) {
uniqueFonts.add(JSON.stringify({ family, style }));
}
await Promise.all(
[...uniqueFonts].map(f => figma.loadFontAsync(JSON.parse(f)))
);
const existing = new Set(
(await figma.getLocalTextStylesAsync()).map(s => s.name)
);
const created = [];
const skipped = [];
for (const [name, family, style, fontSize, lineHeight, cssVar] of defs) {
if (existing.has(name)) {
skipped.push(name);
continue;
}
const ts = figma.createTextStyle();
ts.name = name;
ts.fontName = { family, style };
ts.fontSize = fontSize;
ts.lineHeight = lineHeight ?? { unit: 'AUTO' };
if (cssVar) ts.description = `CSS: var(${cssVar})`;
created.push(name);
}
return { created, skipped };
}
```
Full runnable script:
```javascript
const defs = [
['heading/xl', 'Inter', 'Bold', 48, { unit: 'PIXELS', value: 56 }, '--font-heading-xl'],
['heading/lg', 'Inter', 'Bold', 36, { unit: 'PIXELS', value: 44 }, '--font-heading-lg'],
['body/base', 'Inter', 'Regular', 16, { unit: 'AUTO' }, '--font-body-base'],
['body/sm', 'Inter', 'Regular', 14, { unit: 'AUTO' }, '--font-body-sm'],
['code/base', 'Roboto Mono', 'Regular', 14, { unit: 'AUTO' }, '--font-code-base'],
];
const result = await createTypeRamp(defs);
return result;
```
## Importing Library Text Styles
For text styles from **team libraries**, use `importStyleByKeyAsync`:
```javascript
// Import a library text style by key
const headingStyle = await figma.importStyleByKeyAsync("TEXT_STYLE_KEY");
// Apply to a text node
await textNode.setTextStyleIdAsync(headingStyle.id);
```
`search_design_system` with `includeStyles: true` returns style keys you can import this way. Prefer importing library styles over creating new ones.
## Applying Text Styles to Nodes
```javascript
/**
* Applies a text style to all TEXT nodes on the current page that match a given name pattern.
*
* @param {string} styleId - The ID of a TextStyle.
* @param {string} nodeNamePattern - Substring match against node names.
* @returns {Promise<number>} - Number of nodes the style was applied to.
*/
async function applyTextStyleToMatchingNodes(styleId, nodeNamePattern) {
const textNodes = figma.currentPage.findAllWithCriteria({ types: ['TEXT'] });
let applied = 0;
for (const node of textNodes) {
if (node.name.includes(nodeNamePattern)) {
await node.setTextStyleIdAsync(styleId);
applied++;
}
}
return applied;
}
```
Full runnable script:
```javascript
const applied = await applyTextStyleToMatchingNodes('STYLE_ID', 'Heading');
return { applied };
```
@@ -0,0 +1,82 @@
# Validation Workflow & Error Recovery
> Part of the [use_figma skill](../SKILL.md). How to debug, validate, and recover from errors.
## Contents
- `get_metadata` vs `get_screenshot`
- Error Recovery After Failed `use_figma`
- Recommended Workflow
## `get_metadata` vs `get_screenshot`
After each `use_figma` call, validate results using the right tool for the job. Do NOT reach for `get_screenshot` every time — it is expensive and should be reserved for visual checks.
### `get_metadata` — Use for intermediate validation (preferred)
`get_metadata` returns an XML tree of node IDs, types, names, positions, and sizes. Use it to confirm:
- **Structure & hierarchy**: correct parent-child relationships, component nesting, section contents
- **Node counts**: expected number of variants created, children present
- **Naming**: variant property names follow the `property=value` convention
- **Positioning & alignment**: x/y coordinates, width/height values match expectations
- **Layout properties**: auto-layout direction, sizing mode, padding, spacing
- **Component set membership**: all expected variants are inside the ComponentSet
```
Example: After creating a ComponentSet with 120 variants, call get_metadata on the
ComponentSet node to verify all 120 children exist with correct names, sizes, and positions
— without waiting for a full render.
```
**When to use `get_metadata`:**
- After creating/modifying nodes — to verify structure, counts, and names
- After layout operations — to verify positions and dimensions
- After combining variants — to confirm all components are in the ComponentSet
- After binding variables — to verify node properties (use use_figma to read bound variables if needed)
- Between multi-step workflows — to confirm step N succeeded before starting step N+1
### `get_screenshot` — Use after each major creation milestone
`get_screenshot` renders a pixel-accurate image. It is the only way to verify visual correctness (colors, typography rendering, effects, variable mode resolution). It is slower and produces large responses, so don't call it after every single `use_figma` — but do call it after each major milestone to catch visual problems early.
**When to use `get_screenshot`:**
- **After creating a component set** — verify variants look correct, grid is readable, nothing is collapsed or overlapping
- **After composing a layout** — verify overall structure and spacing
- **After binding variables/modes** — verify colors and tokens resolved correctly
- **After any fix or recovery** — verify the fix didn't introduce new visual issues
- **Before reporting results to the user** — final visual proof
**What to look for in screenshots** — these are the most commonly missed issues:
- **Cropped/clipped text** — line heights or frame sizing cutting off descenders, ascenders, or entire lines
- **Overlapping content** — elements stacking on top of each other due to incorrect sizing or missing auto-layout
- **Placeholder text** still showing ("Title", "Heading", "Button") instead of actual content
## Error Recovery After Failed `use_figma`
**`use_figma` is atomic — failed scripts do not execute.** If a script errors, no changes are made to the file. The file remains in exactly the same state as before the call. There are no partial nodes, no orphaned elements, and retrying after a fix is safe.
**Recovery steps when `use_figma` returns an error:**
1. **STOP — do NOT immediately fix the code and retry.** Read the error message carefully first.
2. **Understand the error.** Most errors are caused by wrong API usage, missing font loads, invalid property values, or referencing nodes that don't exist.
3. **If the error is unclear**, call `get_metadata` or `get_screenshot` to understand the current file state and confirm nothing has changed.
4. **Fix the script** based on the error message.
5. **Retry** the corrected script.
## Recommended Workflow
```
1. use_figma → Create/modify nodes
2. get_metadata → Verify structure, counts, names, positions (fast, cheap)
3. use_figma → Fix any structural issues found
4. get_metadata → Re-verify fixes
5. ... repeat as needed ...
6. get_screenshot → Visual check after each major milestone
⚠️ ON ERROR at any step:
a. Read the error message carefully
b. get_metadata / get_screenshot → If the error is unclear, inspect file state
c. Fix the script based on the error
d. Retry the corrected script (safe — failed scripts don't modify the file)
```
@@ -0,0 +1,375 @@
# Variable & Token API Patterns
> Part of the [use_figma skill](../SKILL.md). How to correctly create, bind, scope, and alias variables using the Plugin API.
>
> For design system context (aliasing strategy, mode decisions, code syntax philosophy, grouping conventions), see [wwds-variables](working-with-design-systems/wwds-variables.md).
## Contents
- Creating Variable Collections and Modes
- Creating Variables (All Types)
- Binding Variables to Node Properties
- Variable Scopes: What They Are and How to Set Them
- Variable Aliasing (VARIABLE_ALIAS)
- Code Syntax (setVariableCodeSyntax)
- Importing Library Variables
- Discovering Existing Variables in the File
- Effect Styles (For Shadows)
## Creating Variable Collections and Modes
```javascript
const collection = figma.variables.createVariableCollection("MyCollection");
// A new collection starts with 1 mode named "Mode 1" — always rename it
collection.renameMode(collection.modes[0].modeId, "Light");
// Add additional modes (returns the new modeId)
const darkModeId = collection.addMode("Dark");
const lightModeId = collection.modes[0].modeId;
```
**Mode limits are plan-dependent:** Free = 1 mode, Professional = up to 4, Organization/Enterprise = 40+. If you need many modes, split across multiple collections.
## Creating Variables (All Types)
`figma.variables.createVariable(name, collection, resolvedType)` — the second argument accepts a collection object or ID string (object preferred).
```javascript
// COLOR — values use {r, g, b, a} (all 01 range, includes alpha)
const colorVar = figma.variables.createVariable("my-color", collection, "COLOR");
colorVar.setValueForMode(modeId, { r: 0.2, g: 0.36, b: 0.96, a: 1 });
// FLOAT — for spacing, radii, sizing, numeric values
const floatVar = figma.variables.createVariable("my-spacing", collection, "FLOAT");
floatVar.setValueForMode(modeId, 16);
// STRING — for font families, font style names, any text value
const stringVar = figma.variables.createVariable("my-font", collection, "STRING");
stringVar.setValueForMode(modeId, "Inter");
// BOOLEAN
const boolVar = figma.variables.createVariable("my-flag", collection, "BOOLEAN");
boolVar.setValueForMode(modeId, true);
```
**Note:** Paint colors use `{r, g, b}` (no alpha), but COLOR variable values use `{r, g, b, a}` (with alpha). Don't mix them up.
## Binding Variables to Node Properties
### Color Bindings (Fills, Strokes)
`setBoundVariableForPaint` returns a **NEW paint** — you must capture the return value:
```javascript
// Create a base paint, bind the variable, assign the result
const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } };
const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", colorVar);
node.fills = [boundPaint];
// Only SOLID paints support color variable binding — gradients/images will throw
```
### Numeric Bindings (Spacing, Radii, Sizing)
`setBoundVariable` binds FLOAT/STRING/BOOLEAN variables to node properties:
```javascript
// Padding
node.setBoundVariable("paddingTop", spacingVar);
node.setBoundVariable("paddingBottom", spacingVar);
node.setBoundVariable("paddingLeft", spacingVar);
node.setBoundVariable("paddingRight", spacingVar);
// Gap
node.setBoundVariable("itemSpacing", gapVar);
node.setBoundVariable("counterAxisSpacing", gapVar);
// Corner radius — use individual corners, NOT cornerRadius
node.setBoundVariable("topLeftRadius", radiusVar);
node.setBoundVariable("topRightRadius", radiusVar);
node.setBoundVariable("bottomLeftRadius", radiusVar);
node.setBoundVariable("bottomRightRadius", radiusVar);
// Size
node.setBoundVariable("width", sizeVar);
node.setBoundVariable("height", sizeVar);
node.setBoundVariable("minWidth", sizeVar);
node.setBoundVariable("maxWidth", sizeVar);
// Other
node.setBoundVariable("opacity", opacityVar);
node.setBoundVariable("strokeWeight", strokeVar);
```
**Not bindable via setBoundVariable:** `fontSize`, `fontWeight`, `lineHeight` — set these directly on text nodes.
### Effect Bindings
```javascript
const effectCopy = JSON.parse(JSON.stringify(node.effects[0]));
const newEffect = figma.variables.setBoundVariableForEffect(effectCopy, "color", colorVar);
// ⚠️ Returns a NEW effect — must capture return value!
node.effects = [newEffect];
// Valid fields: "color" (COLOR), "radius" | "spread" | "offsetX" | "offsetY" (FLOAT)
```
### Applying a Mode to a Frame
```javascript
// All bound children of this frame will resolve to the specified mode's values
frame.setExplicitVariableModeForCollection(collection, modeId);
```
Without this, all nodes use the collection's default (first) mode.
## Variable Scopes: What They Are and How to Set Them
`variable.scopes` controls which Figma property pickers show the variable. The default is `["ALL_SCOPES"]` which shows it everywhere — this is almost never what you want.
```javascript
variable.scopes = ["FRAME_FILL", "SHAPE_FILL"]; // only fill pickers
variable.scopes = ["TEXT_FILL"]; // only text color picker
variable.scopes = ["GAP"]; // only gap/spacing pickers
variable.scopes = ["CORNER_RADIUS"]; // only radius pickers
variable.scopes = []; // hidden from all pickers
```
**All valid scope values:**
`ALL_SCOPES`, `TEXT_CONTENT`, `CORNER_RADIUS`, `WIDTH_HEIGHT`, `GAP`, `ALL_FILLS`, `FRAME_FILL`, `SHAPE_FILL`, `TEXT_FILL`, `STROKE_COLOR`, `STROKE_FLOAT`, `EFFECT_FLOAT`, `EFFECT_COLOR`, `OPACITY`, `FONT_FAMILY`, `FONT_STYLE`, `FONT_WEIGHT`, `FONT_SIZE`, `LINE_HEIGHT`, `LETTER_SPACING`, `PARAGRAPH_SPACING`, `PARAGRAPH_INDENT`
**Always set scopes explicitly**`ALL_SCOPES` is the default but almost never what you want. For a comprehensive scope-to-use-case mapping table, see [token-creation.md § Variable Scopes — Complete Reference Table](../../figma-generate-library/references/token-creation.md).
**Always check the existing file's scope patterns before creating variables** — match whatever convention is already in use. See "Discovering Existing Variables" below.
## Variable Aliasing (VARIABLE_ALIAS)
A variable's value can reference another variable via alias. This is how semantic tokens reference primitive tokens:
```javascript
// Set a variable's value as an alias to another variable
semanticVar.setValueForMode(modeId, {
type: 'VARIABLE_ALIAS',
id: primitiveVar.id
});
```
When the primitive changes, the semantic variable updates automatically across all modes.
## Code Syntax (setVariableCodeSyntax)
Links a Figma variable back to its code counterpart. Call once per platform:
```javascript
variable.setVariableCodeSyntax('WEB', 'var(--color-bg-default)');
variable.setVariableCodeSyntax('ANDROID', 'colorBgDefault');
variable.setVariableCodeSyntax('iOS', 'Color.bgDefault');
// Read back: variable.codeSyntax → { WEB: '...', ANDROID: '...', iOS: '...' }
```
**When deriving CSS names from Figma names**, replace both slashes AND spaces with hyphens:
```javascript
// WRONG — leaves spaces in CSS variable name
`var(--${figmaName.replace(/\//g, '-').toLowerCase()})`
// CORRECT — replace all whitespace and slashes
`var(--${figmaName.replace(/[\s\/]+/g, '-').toLowerCase()})`
// BEST — use the original CSS variable name from the source, not a derived one
`var(${token.cssVar})`
```
## Importing Library Variables
For variables from **team libraries** (not the current file), use `importVariableByKeyAsync`:
```javascript
// Import a single variable by its key
const colorVar = await figma.variables.importVariableByKeyAsync("VARIABLE_KEY");
// Now use it like any local variable
const paint = figma.variables.setBoundVariableForPaint(
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', colorVar
);
node.fills = [paint];
```
To discover available library variable collections and their variables:
```javascript
// List all available library variable collections
const libCollections = await figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync();
// Each has: name, key, libraryName
// Get variables in a specific library collection
const libVars = await figma.teamLibrary.getVariablesInLibraryCollectionAsync(libCollections[0].key);
// Each has: name, key, resolvedType
// Import the ones you need:
const imported = await figma.variables.importVariableByKeyAsync(libVars[0].key);
```
**When to import vs. use local:** If `variable.remote === true`, it's from a library — you can reference it directly if already imported, or import by key. If `remote === false`, it's local to the file — use `getVariableByIdAsync` directly.
## Discovering Existing Variables in the File
**Always inspect the file's existing variables before creating new ones.** Different files use different naming conventions, scope patterns, and collection structures. Match what's already there.
### List collections with mode info
```javascript
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 => ({ name: m.name, id: m.modeId }))
}));
return results;
```
### Inspect scope patterns used in existing variables
```javascript
const collections = await figma.variables.getLocalVariableCollectionsAsync();
const scopeGroups = {};
for (const c of collections) {
for (const id of c.variableIds) {
const v = await figma.variables.getVariableByIdAsync(id);
const key = JSON.stringify(v.scopes);
if (!scopeGroups[key]) scopeGroups[key] = [];
scopeGroups[key].push(v.name);
}
}
return scopeGroups;
```
### Build a name→variable lookup for reuse
```javascript
const varByName = {};
for (const v of await figma.variables.getLocalVariablesAsync()) {
varByName[v.name] = v;
}
// Bind to existing variable by name — no hex values needed
function bindFill(node, varName) {
const v = varByName[varName];
if (!v) throw new Error(`Variable not found: ${varName}`);
const paint = figma.variables.setBoundVariableForPaint(
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', v
);
node.fills = [paint];
}
```
**Only create new variables for tokens that have no match in the file.** After building the lookup, compare against the needed tokens and create variables only for the delta.
## Listing Collections with Full Variable Details
The async API returns richer data including code syntax and scopes per variable:
```javascript
/**
* Lists all local variable collections defined in the current Figma file,
* including metadata for their modes and variables.
*
* @returns {Promise<Array<{
* name: string,
* id: string,
* modes: Array<[name: string, modeId: string]>,
* variables: Array<[name: string, id: string, codeSyntax: object, scopes: string[]]>
* }>>}
*/
async function listVariableCollectionsAndVariables() {
const collections = await figma.variables.getLocalVariableCollectionsAsync();
const results = [];
for (const collection of collections) {
const vars = [];
for (const id of collection.variableIds) {
const v = await figma.variables.getVariableByIdAsync(id);
vars.push([v.name, v.id, v.codeSyntax, v.scopes]);
}
results.push({
name: collection.name,
id: collection.id,
modes: collection.modes.map(m => [m.name, m.modeId]),
variables: vars
});
}
return results;
}
```
Full runnable script:
```javascript
const results = await listVariableCollectionsAndVariables();
return results;
```
## Setting and Removing Code Syntax
Must be executed in the file the variable is defined in:
```javascript
/**
* Set the code syntax for a variable for a specific platform.
*
* @param {string} variableId
* @param {'WEB'|'ANDROID'|'iOS'} platform
* @param {string} syntax
*/
async function setVariableCodeSyntax(variableId, platform, syntax) {
const variable = await figma.variables.getVariableByIdAsync(variableId);
variable.setVariableCodeSyntax(platform, syntax);
}
/**
* Remove code syntax for a variable for one or more platforms.
*
* @param {string} variableId
* @param {Array<'WEB'|'ANDROID'|'iOS'>} platforms — defaults to all three
*/
async function removeVariableCodeSyntax(variableId, platforms = ["WEB", "ANDROID", "iOS"]) {
const variable = await figma.variables.getVariableByIdAsync(variableId);
for (const platform of platforms) {
variable.removeVariableCodeSyntax(platform);
}
}
/**
* Set a value for a variable in a specific mode.
* For aliases, value must be: { type: 'VARIABLE_ALIAS', id: '<variableId>' }
*
* @param {string} variableId
* @param {string} modeId
* @param {string|number|boolean|RGB|RGBA|{type: 'VARIABLE_ALIAS', id: string}} value
*/
async function setVariableValueForMode(variableId, modeId, value) {
const variable = await figma.variables.getVariableByIdAsync(variableId);
variable.setValueForMode(modeId, value);
}
```
## Effect Styles (For Shadows)
Shadows can't be stored as variables. Use effect styles. For comprehensive patterns, see [effect-style-patterns.md](effect-style-patterns.md).
```javascript
const shadow = figma.createEffectStyle();
shadow.name = "Shadow/Subtle";
shadow.effects = [{
type: "DROP_SHADOW",
color: { r: 0, g: 0, b: 0, a: 0.06 },
offset: { x: 0, y: 2 },
radius: 8,
spread: 0,
visible: true,
blendMode: "NORMAL"
}];
// Apply to a node
frame.effectStyleId = shadow.id;
```
@@ -0,0 +1,17 @@
# Working with design systems: Creating Components
When creating Figma components, you need to start by understanding the source and its intent.
If the user is asking you to create a component based on a design or specification, you need to understand the property model before you build anything. What variants are needed? What text, boolean, or instance swap properties exist? Getting the structure right upfront matters because restructuring a component after instances exist is destructive.
If you are given a code component as reference (React props, tokens, etc.), your goal is to reflect the property surface as closely as makes sense in Figma's model. Not all code properties translate directly — hover and focus states are not props in web code, but they are variants in Figma. Understand those gaps and make deliberate decisions about how to represent them.
Variants are the most important thing to get right. Each combination of variant values creates a node on the canvas. Redundant combinations still exist as explicit nodes — there is no way to conditionally exclude them. Define only the axes you actually need.
Non-variant properties (text, boolean, instance swap) should be added after the variant structure is established. These are defined at the component/component set level and referenced by descendant nodes via `componentPropertyReferences`. Always connect them — a property that isn't wired to a descendant is invisible to users of the component.
If the user asks you to make architectural decisions, lean toward fewer variants and more boolean/text properties where possible. Variants multiply combinatorially; the other property types do not. An optional slot property in code might be a combination of instance swap and boolean visibility.
When naming properties, casing is less important since translation layers like Code Connect can do the mapping to represent the code form. Feel free to take a sentence or capitalized case approach for better readability in Figma.
Keep in mind that components often need to be published and connected to Code Connect for the full design-to-code workflow to work. Creating the component is only one part of the system.
@@ -0,0 +1,17 @@
# Working with design systems: Using Components
When using Figma components, you need to start by understanding the state of the source and the state of Figma.
For the source, you need to know what component is being referenced. This could come from a component key, a node ID, a name, or a Code Connect mapping. If you have a component key from a design system library, prefer `importComponentByKeyAsync` over finding by name, since names are not unique. If you only have a name, search the page or use `search_design_system` to find the right match.
For Figma, you need to know whether the component is local or in a library. Local components can be accessed directly by node ID. Published library components must be imported first — `importComponentByKeyAsync` or `importComponentSetByKeyAsync` — before an instance can be created.
Before setting properties on an instance, read `componentPropertyDefinitions` from the main component first. Property names are not simple strings — TEXT, BOOLEAN, and INSTANCE_SWAP properties have a `#uid` suffix (e.g. `"Label#1234"`). Only VARIANT properties are plain names (e.g. `"Size"`). Using the wrong key in `setProperties` will silently do nothing.
A component might have multiple text properties, which are not possible to derive from text node layer names. Look to the properties to help you understand what values to set, rather than thinking of setting text node characters directly.
When you need to set a nested instance swap (e.g. an icon property), you need the component key of the swap target, not just its name. Import the target component and pass its node ID.
Be aware that instances inside other instances are nested and changes made to a nested instance may be treated as overrides. If the intent is to change the default appearance, you need to modify the main component, not the instance.
When selecting which variant to use, read the `componentProperties` on the instance to see the current state, and `componentPropertyDefinitions` on the main component to see all available options.
@@ -0,0 +1,50 @@
# Components
Components overlap a lot with the idea of components in a codebase, but with some gaps and other Figma-specific use cases. Components in Figma can be reusable entities that do not have a comparable library pattern, or they can be published and distributed in a library that is aligned to a code forms.
Properties can vary from code in different ways, but alignment to code can still happen without a direct relationship. For example, an interactive pattern in code (like a button) can have many states. A lot of these states (active, focused etc) would be expressed in Figma as variants, which is a concept more closely aligned to properties in a code library. In the case of web this is confusing since hover is not a prop, it is a pseudo selector. At the same time, a color variant might be perfectly aligned between design and code (a property in both places). These discrepancies are accounted for in translation with Figma's Code Connect (deterministic context mapping), but in the case of these tools, must be understood to be properly used.
Figma has four property types, which can be inspected in the component definition's `componentPropertyDefinitions`. To fully understand the component, its descendants must be traversed. Property types include:
- Variant
- This is reflected as permutations of the component in a Component Set on the canvas. Each variant is explicitly visualized, including an redundant permutations ("Small + Primary + Disabled" may look the same as "Small Secondary Sisabled"). These permutations create different variants implicitly in Figma and it is handled through layer naming (`Variant=Primary,Size=Small,State=Disabled`).
- Text/String
- Text properties are stored on the component parent, but can be mapped to Text node descendants.
- `node.componentPropertyReferences.characters` on a descendant text node are how you determine where the text property is referenced (can be multiple, though unlikely).
- Boolean
- Boolean properties are stored on the component parent, but can be mapped to any node descendant that can have its visibility toggled.
- `node.componentPropertyReferences.visible` on a descendant node are how you determine where the boolean property is referenced.
- Instance Swap
- Instance swap properties are stored on the component parent, but can be mapped to Instance node descendants.
- `node.componentPropertyReferences.mainComponent` on a descendant instance node are how you determine where the instance property is referenced. A classic example of this is an icon property.
## Descriptions
Components, component sets, and instances all inherit `PublishableMixin`, which includes a writable `description` string. Setting a description is important for any component intended to be used by others — it appears in Figma's dev mode and component panel, and is surfaced in MCP context when reading component metadata.
Descriptions should explain the component's intent and any non-obvious usage constraints. They are not a substitute for Code Connect annotations, but they are always visible without any tooling setup.
```js
component.description =
"Primary action button. Use for the single most important action on a page.";
```
Variant components (children of a component set) also have a `description` field, but in practice the component set description is what users see. Set it on the component set, not on individual variant nodes.
To read descriptions when auditing:
```js
// Get all component sets and their descriptions
figma.root
.findAllWithCriteria({ types: ["COMPONENT_SET"] })
.map((n) => ({ name: n.name, description: n.description }));
```
## Usage guidelines
- [Creating components](wwds-components--creating.md): What you must consider when creating new components.
- [Using components](wwds-components--using.md): What you must consider when trying to use the right components.
## Code patterns
For runnable code examples (creating, importing, discovering, inspecting components), see [component-patterns.md](../component-patterns.md).
@@ -0,0 +1,52 @@
# Working with design systems: Effect Styles
Effect styles in Figma are named, reusable definitions of one or more visual effects — drop shadows, inner shadows, and blurs. They are the closest equivalent to a shadow or elevation token in a design system.
Effect styles are distinct from variables. There is no single variable type that represents a shadow. However, individual numeric and color properties within an effect _can_ be bound to variables, allowing shadow values to participate in a token system.
## Model
An `EffectStyle` has one core writable property beyond the base style fields:
| Property | Type | Notes |
| ------------- | ----------------------- | ----------------------------------------------------- |
| `name` | `string` | Slash-delimited for grouping (e.g. `"Elevation/200"`) |
| `effects` | `ReadonlyArray<Effect>` | **Read-only array** — clone, modify, reassign |
| `description` | `string` | Inherited from `BaseStyleMixin` |
### Effect types
An `Effect` is a discriminated union. The most common types:
| `type` | Key properties |
| ----------------- | ---------------------------------------------------------------------------------------------------- |
| `DROP_SHADOW` | `color: RGBA`, `offset: Vector`, `radius: number`, `spread: number`, `visible: boolean`, `blendMode` |
| `INNER_SHADOW` | Same as `DROP_SHADOW` |
| `LAYER_BLUR` | `radius: number`, `visible: boolean` |
| `BACKGROUND_BLUR` | `radius: number`, `visible: boolean` |
All colors are in 01 range (`RGBA`), not 0255.
### Variable bindings on effects
Effect properties that can be bound to variables (via `setBoundVariableForEffect(effect, field, variable)` on a node, or inline when constructing):
`color`, `radius`, `spread`, `offsetX`, `offsetY`
Note: `setBoundVariableForEffect` returns a **new** effect object — you must capture it and reassign the `effects` array.
### Applying an effect style to a node
Assign the style's `id` to the node's `effectStyleId`. The node's `effects` property will then reflect the style's values.
## Common gotchas
- **`effects` is read-only**: You cannot mutate the array in place. Clone it, modify the clone, then reassign: `style.effects = [...style.effects, newEffect]`.
- **Effects stack in order**: The order of effects in the array matters visually. Drop shadows render bottom-to-top.
- **Colors are RGBA 01**: `{ r: 0, g: 0, b: 0, a: 0.15 }` — not hex, not 0255.
- **`getLocalEffectStyles()` is deprecated**: Always use `getLocalEffectStylesAsync()`.
- **Styles are not automatically applied**: Creating an `EffectStyle` has no effect on any node until you assign its ID to a node.
## Code patterns
For runnable code examples (listing, creating, applying effect styles), see [effect-style-patterns.md](../effect-style-patterns.md).
@@ -0,0 +1,90 @@
# Working with design systems: Text Styles
Text styles in Figma are named, reusable typography definitions. They are the closest equivalent to a type ramp in a design token library. A text style bundles font family, size, weight, line height, letter spacing, and other typographic properties into a single named entity that can be applied to text nodes.
Text styles are distinct from variables. You cannot put typography into a single variable — there is no composite variable type. However, individual properties on a text style _can_ be bound to variables (e.g. binding `fontSize` to a size variable, or `fontFamily` to a string variable), which allows the style to participate in a token system.
## Model
A `TextStyle` has the following writable properties:
| Property | Type | Notes |
| ------------------ | ---------------- | ---------------------------------------------------------------------------- |
| `name` | `string` | Slash-delimited for grouping (e.g. `"Heading/XL"`) |
| `fontSize` | `number` | In pixels |
| `fontName` | `FontName` | `{ family: string, style: string }`**font must be loaded before setting** |
| `letterSpacing` | `LetterSpacing` | `{ value: number, unit: 'PIXELS' \| 'PERCENT' }` |
| `lineHeight` | `LineHeight` | `{ value: number, unit: 'PIXELS' \| 'PERCENT' }` or `{ unit: 'AUTO' }` |
| `textCase` | `TextCase` | `'ORIGINAL' \| 'UPPER' \| 'LOWER' \| 'TITLE' \| 'SMALL_CAPS'` |
| `textDecoration` | `TextDecoration` | `'NONE' \| 'UNDERLINE' \| 'STRIKETHROUGH'` |
| `paragraphSpacing` | `number` | |
| `paragraphIndent` | `number` | |
| `description` | `string` | Inherited from `BaseStyleMixin` |
### lineHeight and letterSpacing format
These properties must be objects — not bare numbers:
```js
// WRONG — bare number throws
style.lineHeight = 1.5;
style.letterSpacing = 0;
// CORRECT
style.lineHeight = { unit: "AUTO" }; // auto line height
style.lineHeight = { value: 24, unit: "PIXELS" }; // fixed pixel height
style.lineHeight = { value: 150, unit: "PERCENT" }; // 150% line height
style.letterSpacing = { value: 0, unit: "PIXELS" }; // zero tracking
style.letterSpacing = { value: -2, unit: "PIXELS" }; // tight tracking
style.letterSpacing = { value: 5, unit: "PERCENT" }; // percent-based tracking
```
When reading a `lineHeight` back, always check `unit` first — `{ unit: 'AUTO' }` has no `value` key.
### Variable bindings on text styles
The following fields can be bound to variables via `style.setBoundVariable(field, variable)`:
`fontFamily`, `fontSize`, `fontStyle`, `fontWeight`, `letterSpacing`, `lineHeight`, `paragraphSpacing`, `paragraphIndent`
To unbind: `style.setBoundVariable(field, null)`
**Important: `setBoundVariable` is NOT available on `TextStyle` in headless `use_figma` mode.**
It is only available in interactive plugin context (UI plugins, Figma editor). When running through `use_figma` (MCP, assistant headless runtime), calling `ts.setBoundVariable(...)` will throw `"not a function"`. In this context, set raw values directly instead:
```js
// In use_figma (headless) — variable binding not available
const ts = figma.createTextStyle();
ts.fontSize = 24; // set directly; cannot bind to a variable
// In a real interactive plugin — variable binding works
const ts = figma.createTextStyle();
ts.setBoundVariable("fontSize", fontSizeVariable);
```
If live variable binding on text styles is required, the recommended approach is to:
1. Create the text styles with raw values via `use_figma`
2. Open the file in Figma and bind variables interactively via the Styles panel, OR
3. Use an interactive plugin that runs in the Figma editor (not headless)
### Applying a text style to a node
Once you have a `TextStyle`, apply it to a `TextNode` by assigning its `id` to the node's `textStyleId` property. You can also use the async setter `setTextStyleIdAsync(id)`. Setting `textStyleId` on a node does **not** require the font to be loaded — only editing the text content or font properties directly does.
## Common gotchas
- **Font must be loaded before setting `fontName`**: Call `await figma.loadFontAsync({ family, style })` before creating or modifying a text style's font.
- **Font style names are file-dependent**: Font style names like `"SemiBold"` vs `"Semi Bold"` vary by font provider and Figma file. Always probe by calling `loadFontAsync` and catching errors to discover the correct style string rather than guessing.
- **`setBoundVariable` not available headless**: `TextStyle.setBoundVariable()` throws `"not a function"` in `use_figma` / headless mode. Set raw values instead and bind interactively if needed.
- **Styles are not automatically applied**: Creating a `TextStyle` has no effect on any node until you assign its ID to a text node.
- **`getLocalTextStyles()` is deprecated**: Always use `getLocalTextStylesAsync()`.
- **Names are not unique**: Two text styles can share the same name. Match by ID or `key` when looking up a known style, not by name alone.
- **Slash grouping is visual only**: `"Heading/XL"` and `"HeadingXL"` are different names; the slash is just a UI affordance.
- **`lineHeight` and `letterSpacing` must be objects**: `style.lineHeight = 1.5` throws. Always use `{ value, unit }` format or `{ unit: 'AUTO' }`.
## Code patterns
For runnable code examples (listing, creating, probing fonts, type ramps, applying styles), see [text-style-patterns.md](../text-style-patterns.md).
@@ -0,0 +1,13 @@
# Working with design systems: Creating Variables
When creating Figma variables, you need to start by understanding the state of the source data.
If the user is asking you to create variables based on values, they likely want you to indicate the structure. Whether or not you use semantic aliasing primitive will be based on the inputs you are given about the source data.
If you are given code inputs (JSON, CSS, etc) your goal should be to reflect the existing patterns as closely as possible, but also embrace the design context as distinct from code. For example, casing is less important since you have code syntax that can directly represent the code form. Feel free to take a sentence or capitalized case approach for better readability in Figma.
It is important to understand the underlying structure before you create anything. If there is an implied aliased setup, you want to get that right. You may also need to anticipate modes to know how to split things up. Sizes and Colors likely have different mode requirements in complex systems, so you want to consider that as you create the structure.
If someone asks you to just make a decision based on best practices, that answer will be relative to the complexity of the environment. A simple theme is great best practice for simple needs. Similarly, a complex extended collection setup for someone on an enterprise plan might also be best practice as well.
Keep in mind that systems might also require you to handle text and effect styles for some of the things specified in token libraries.
@@ -0,0 +1,13 @@
# Working with design systems: Using Variables
When using Figma variables, you need to start by understanding the state of the source and the state of Figma.
For the source, you need to know the breadth of variables code representation. CSS, JSON, theme providers etc will all be able to indicate what the user will expect you to cover in Figma. Some beginner users might not even know what does and doesn't exist in Figma, and if you cant discover that on your own, you will need their help making the right decision.
For Figma, you need to know what collections exist, what their modes are, and what values and names and code syntaxes are in them. This will help you make sure you are using the right things. For properties that "should" have variables but don't, you likely will need to ask the user what to do. Your understanding of Figma's current state should come first.
You can use code syntax and your understanding of the environment you are expected to be referencing to know which variable in Figma to use. You can also use Figma's variable scopes as indicators if they are specified. It is best to audit those up front.
When using variables you should also be aware of mode mismatches, the default mode in Figma may not be the mode referenced by the user in their expectations. Similarly, many collections may refer to values, but the most specific collection is what you should be using. For example, a semantic collection that aliases a primitive collection, the semantic collection would be what you reference. A component token collection (eg. button/background/primary) might alias a semantic collection, and it is the component collection you need to reference. In some other examples, there may be no aliasing and you're simply value matching.
Gap and padding values for frames are really important and often have to be interpreted semantically or based on layout component values.
@@ -0,0 +1,64 @@
# Working with design systems: Variables
Variables overlap a lot with the idea of tokens in a codebase, but with some gaps and other Figma-specific use cases. Variables are single value, number, string, color, boolean.
In Figma you can do conditional logic and use variables to get basic prototyping functionality. String values can also be used as sophisticated placeholder setups that have different modes for different languages. Not everything you use a variable for in Figma would be used exactly the same way in code. However, for design systems, they are often synced to code in some way.
One gap is the lack of composite tokens. You can't put a box shadow behind a single variable. That is an [effect style](wwds-effect-styles.md), but style values can be bound to variables. Similarly for a type ramp, you have to use [Text Styles](wwds-text-styles.md).
## Model
### Collections
Collections can be thought of a groups in Figma. An example Collection would be "Colors" where there might be a light and dark "Mode." Each value would have two definitions.
### Extended Collections
Extended collections allow you to create a colleciton based on another collection and only override _some_ of the values. Just like inheritance and overrides in CSS. This aligns well for scenarios like branded color themes.
### Modes
Modes in Figma can be thought of like light and dark, but users can specify modes for anything, including sizes, languages (string variables exist in Figma too).
### Aliasing
Aliasing in Figma variables is simply when you point a variable to another variable. Common example is pointing a semantic variable to a primitive variable. Some teams also do component level tokens which adds a third component specific layer.
**Decision rule:** If the source data has two tiers (primitives + semantics), create all primitives first, then create semantic variables that alias into them. If the source data is a single flat tier, create flat variables with no aliases. When in doubt, ask.
### Code Syntax
Code syntax is a surface area in Figma for codebase translation context. You can set WEB, iOS, and ANDROID code syntax on any variable, and when that variable is referenced in other places (visually in Figma's dev mode, as design context via MCP), this codebase form will appear. These are best thought of as "instance" documentation, eg. `var(--the-thing)` instead of `--the-thing` in the case of CSS.
### Scope
`variable.scopes: VariableScope[]` specifies which properties in Figma the variable can be used for. This is important when you create and when you use variables. **Always set specific scopes rather than leaving the default `ALL_SCOPES`** — it pollutes every property picker with irrelevant tokens. The more specific the better. For the canonical scope-to-use-case mapping, see [token-creation.md § Variable Scopes — Complete Reference Table](../../figma-generate-library/references/token-creation.md).
Common scope values:
- `ALL_SCOPES` — unrestricted; **avoid this** — it is the default but almost never the right choice. Only acceptable for very simple files with a handful of variables where the overhead of precise scoping isn't justified
- `FRAME_FILL`, `SHAPE_FILL`, `TEXT_FILL`, `STROKE_COLOR` — color bindings (use specific fill scopes; `ALL_FILLS` covers all three fill scopes together)
- `TEXT_CONTENT` — string variables for text layers
- `FONT_SIZE`, `FONT_WEIGHT`, `LINE_HEIGHT`, `LETTER_SPACING` — typography
- `CORNER_RADIUS`, `WIDTH_HEIGHT`, `GAP` — layout/spacing
- `OPACITY` — layer opacity
### Grouping
Variable names in Figma are slash delimited and each slash represents a group that is visualized in Figma. When you are doing matching, consider a part of a code prefix might be the name of the collection, not a top level group. Sometimes you will have prefixes in code that aren't in Figma, and that can be ok, just be sure to ask if it is unclear. You can always validate existing variables by referencing the code syntax.
## Common gotchas
- **`createVariableCollection` always creates a default mode** — you will need to rename it (or delete it and add your own) rather than creating from scratch.
- **Duplicate variable names throw silently** — Figma does not error; it creates a second variable with the same name. Always check for existence before creating.
- **Variable aliases require the target to be in the same file** — cross-file aliasing is not supported via the plugin API. If you need to alias to a library variable, import it first.
- **`setValueForMode` with an alias requires the exact shape** — `{ type: 'VARIABLE_ALIAS', id: '<variableId>' }`. Any deviation will silently set the wrong value or throw.
## Usage guidelines
- [Creating variables](wwds-variables--creating.md): What you must consider when creating new variables.
- [Using variables](wwds-variables--using.md): What you must consider when trying to use the right variables.
## Code patterns
For runnable code examples (creating collections, binding variables, scopes, aliasing, discovering existing variables), see [variable-patterns.md](../variable-patterns.md).
@@ -0,0 +1,41 @@
# Working with design systems
When working with design systems in Figma, there can be many nuances when deciding how to do the right thing. Figma's model for patterns is form-agnostic, this is one of its strengths, allowing people to refer to a pattern in a spec that may take distinct forms in different codebases. However, this can result in complex procedures and nuances when translating something to Figma and back. Figma has components, tokens, and other reusable patterns (text and effect styles, prototyping actions, etc). The way that Figma's paradigms function can be difficult to translate one to one.
To make translation of patterns work between design and code forms, it is important that teams think about alignment while also embracing the function of representation (design) and implementation (production) forms independently as complementary pieces of a shared puzzle.
For the process of design, the desirable state of a system is something that is structured with experimentation in mind, something that is highly visual, easy to iterate, test, and confirm new ideas. Depending on the product and team, exploration might be mandatory to be done within the confines of an existing system, for other scenarios, exploration of new territory is the priority, a place for the system to grow into, or a new system to be made. Figma's platform allows for teams to validate, align, and collaborate on new ideas, then solidify them in product designs, which are ultimately specs. That work is supported by design libraries and that process can include code in prototypes and other less permanent forms as much as it does Figma's native paradigms.
For the process of implementation, the desirable state for production code is rigidity, efficiency, and related to secure data and functional layers. Developer experience implementing a design and the designer experience surfacing and committing to an idea are paths from distinct points of to the same shared outcome. Their optimization looks different, and that is reflected when you engage with Figma's APIs.
The key is not to avoid gaps, but to make sure they are definitively bridgable. Translation layers help agents and people go between representational and production forms.
The Figma paradigms you will need to understand when working with design systems. In each file below there will be further links to instructions for using and creating:
- [Components](wwds-components.md)
- [Variables](wwds-variables.md)
- [Effect Styles](wwds-effect-styles.md)
- [Text Styles](wwds-text-styles.md)
Things you might be asked to do with respect to design systems:
- Create patterns in Figma that match patterns in code
- Likely (but not exclusively) to get up to speed so that visual riffing can be done in Figma
- Create variables based on a stylesheet, JSON format, some other theme definition
- Create Figma text styles that match a type hierarchy defined somewhere
- Create components based on existing code components
- Sync between code and design forms
- Making sure that Figma's concepts match a production form
- Use an existing Figma design library to create something
- This something could be matching an existing code form, an image, or just a prompt
- Clean up a design to match some code pattern
## Things to remember
Many people will use these tools to try out ideas, and not everything you get asked to do will feel realistic for the environment you are running in. It is important to contextualize that, but then also know when you are definitively working in a production environment and there is a very real task you need to perform consistently.
Not everyone asking you to do something knows what they should be doing. You must figure out if the request is to generically perform design systems actions, uphold existing the rules that are codified in Figma or in a codebase, demonstrate an idea, enforce existing guidelines, etc.
Not every environment you are working in has the same degree of expertise and maturity. Some systems will be very complex and the priority and have a lot of things to parse through to get to the right outcome. Some scenarios will be very immature and even starting from scratch. Something as simple as creating a component could be very elementary or very sophisticated depending on the environment. The instructions you find here are attempting to be unbiased.
For example, how you reflect the "hover" state of a button could be left entirely up to you to make a reasonable decision for a user that is playing around with getting a decent example scaffolded using best practices, but it could also be something that exists definitively in the codebase and you need to go match it. That codebase definition could be refering to design tokens that do not yet exist in code that change dark and light mode values. In this second example you are now needing to do a bunch of variables work just to add a hover state to a component with proper dark and light mode support, where in the first scenario, you can kinda just do whatever is easiest. This is the line you will be walking, and making good judgement here is about doing whatever is the smartest thing in the environment you are in.