# 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 0–1 range, includes alpha) const colorVar = figma.variables.createVariable("my-color", collection, "COLOR"); colorVar.setValueForMode(modeId, { r: 0.2, g: 0.36, b: 0.96, a: 1 }); // FLOAT — for spacing, radii, sizing, numeric values const floatVar = figma.variables.createVariable("my-spacing", collection, "FLOAT"); floatVar.setValueForMode(modeId, 16); // STRING — for font families, font style names, any text value const stringVar = figma.variables.createVariable("my-font", collection, "STRING"); stringVar.setValueForMode(modeId, "Inter"); // BOOLEAN const boolVar = figma.variables.createVariable("my-flag", collection, "BOOLEAN"); boolVar.setValueForMode(modeId, true); ``` **Note:** Paint colors use `{r, g, b}` (no alpha), but COLOR variable values use `{r, g, b, a}` (with alpha). Don't mix them up. ## Binding Variables to Node Properties ### Color Bindings (Fills, Strokes) `setBoundVariableForPaint` returns a **NEW paint** — you must capture the return value: ```javascript // Create a base paint, bind the variable, assign the result const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }; const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", colorVar); node.fills = [boundPaint]; // Only SOLID paints support color variable binding — gradients/images will throw ``` ### Numeric Bindings (Spacing, Radii, Sizing) `setBoundVariable` binds FLOAT/STRING/BOOLEAN variables to node properties: ```javascript // Padding node.setBoundVariable("paddingTop", spacingVar); node.setBoundVariable("paddingBottom", spacingVar); node.setBoundVariable("paddingLeft", spacingVar); node.setBoundVariable("paddingRight", spacingVar); // Gap node.setBoundVariable("itemSpacing", gapVar); node.setBoundVariable("counterAxisSpacing", gapVar); // Corner radius — use individual corners, NOT cornerRadius node.setBoundVariable("topLeftRadius", radiusVar); node.setBoundVariable("topRightRadius", radiusVar); node.setBoundVariable("bottomLeftRadius", radiusVar); node.setBoundVariable("bottomRightRadius", radiusVar); // Size node.setBoundVariable("width", sizeVar); node.setBoundVariable("height", sizeVar); node.setBoundVariable("minWidth", sizeVar); node.setBoundVariable("maxWidth", sizeVar); // Other node.setBoundVariable("opacity", opacityVar); node.setBoundVariable("strokeWeight", strokeVar); ``` **Not bindable via setBoundVariable:** `fontSize`, `fontWeight`, `lineHeight` — set these directly on text nodes. ### Effect Bindings ```javascript const effectCopy = JSON.parse(JSON.stringify(node.effects[0])); const newEffect = figma.variables.setBoundVariableForEffect(effectCopy, "color", colorVar); // ⚠️ Returns a NEW effect — must capture return value! node.effects = [newEffect]; // Valid fields: "color" (COLOR), "radius" | "spread" | "offsetX" | "offsetY" (FLOAT) ``` ### Applying a Mode to a Frame ```javascript // All bound children of this frame will resolve to the specified mode's values frame.setExplicitVariableModeForCollection(collection, 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, * 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: '' } * * @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; ```