DOL Figma AI Workflows
Runbooks for the 5 most-common Figma AI tasks. Each workflow has:
- Precondition - what must be true before starting
- Steps - sequence of MCP calls / decisions
- Verify - deterministic check that the step worked
- Failure modes - what goes wrong + recovery
🛑 Match-and-refuse (workflow entry-level): nếu bạn sắp run workflow mà KHÔNG biết precondition status (MCP connected? cache fresh? variable ID known?), STOP - check precondition block trước. Skipping precondition = silent runtime failure ở middle-of-workflow (hardest to debug). Prefer
lookup.py+ explicit verify BEFORE side-effect operations.
Workflow 1 - Bind a variable to fill / text-color / icon-color / border-color
Section titled “Workflow 1 - Bind a variable to fill / text-color / icon-color / border-color”When to use: any design task where a color value needs to come from DS rather than hardcoded hex.
📌 Also available as Claude Code skill:
.claude/skills/figma-bind-variable/SKILL.md(R23 extraction). Skill auto-invokes when task phrase matches “bind variable”, “apply token”, “set fill color”; lighter than reading this whole file. This workflow remains canonical reference; skill is the precise-task lean entry.
Precondition
Section titled “Precondition”- Know what you’re styling (fill / text / icon / border) -
variable-selection.md§1 - Know semantic role (brand / neutral / info / success / warning / danger / special / ai) - §2
- Know intensity (dim / soft / primary / secondary / subtle) - §3
- figma-console-mcp connected; variable collection loaded
Preferred path - CLI lookup (R18+)
Section titled “Preferred path - CLI lookup (R18+)”# Get Figma ID + collection + scopes for a variable in <500 tokens:python3 ../tools/lookup.py variable text-color/danger-primaryThen bind:
const target = await figma.variables.getVariableByIdAsync('VariableID:18484:36683');node.setBoundVariable('fills', target);Alternative - grep variable-id-map.md (R17+)
Section titled “Alternative - grep variable-id-map.md (R17+)”If lookup.py unavailable, grep variable-id-map.md for the variable name (file is sectioned by collection). Same direct binding pattern.
Fallback path - runtime name lookup
Section titled “Fallback path - runtime name lookup”If id-map stale OR cross-file work where IDs differ:
- Pick the variable name by resolving Category × Family × Level in
variable-selection.md§4 - Verify it exists - grep
variable-registry.mdfor the full name. If not found → anti-patternfigma-naming-6-binding#05(don’t invent) - Resolve Figma variable ID via MCP at runtime:
const vars = await figma.variables.getLocalVariablesAsync();const target = vars.find(v => v.name === 'text-color/neutral-primary');// Note: Figma uses slash-separated name (text-color/neutral-primary), CSS uses hyphen (--text-color-neutral-primary)
- Bind to node property:
const node = figma.currentPage.selection[0]; // or specific node lookup// For text-color:node.setBoundVariable('fills', target);// For fill (frame/shape bg):node.setBoundVariable('fills', target);// For border (stroke):node.setBoundVariable('strokes', target);
- Read back
boundVariablesto confirm:node.boundVariables.fillsshould show the variable ID.
Verify
Section titled “Verify”node.boundVariables[<property>]contains the variable ID (notundefined)- Screenshot the node → color matches
resolvedValuefrom registry - Switch Figma mode (GM ↔ KM / light ↔ dark) → color changes if
darkValuediffers, stays same otherwise
Failure modes
Section titled “Failure modes”| Symptom | Cause | Recovery |
|---|---|---|
target is undefined after lookup | Variable name mismatch (hyphen vs slash, wrong category prefix) | Re-read variable-registry.md; Figma uses /, CSS uses - |
setBoundVariable throws | Wrong property name (e.g., fill instead of fills) | Property names are plural: fills, strokes, effects |
| Color renders but doesn’t change on mode switch | Hardcoded hex instead of bound variable | Check boundVariables; if empty, bind the variable explicitly |
Icon uses text-color/* instead of icon-color/* | Category mismatch | figma-naming-6-binding#02: re-bind with icon-color/* |
Workflow 2 - Extract boundVariables from reference component
Section titled “Workflow 2 - Extract boundVariables from reference component”When to use: building a new component similar to existing one. Shortest path to DS-aligned bindings.
Precondition
Section titled “Precondition”- Know the reference component’s node ID (from Figma selection or MCP query)
- figma-console-mcp connected
- Get the reference node:
const ref = figma.root.findOne(n => n.id === '<nodeId>');// OR from current selection:const ref = figma.currentPage.selection[0];
- Read all bound variables:
const recurse = (node, acc = {}) => {if (node.boundVariables && Object.keys(node.boundVariables).length > 0) {acc[node.name] = node.boundVariables;}if (node.children) node.children.forEach(c => recurse(c, acc));return acc;};const bindings = recurse(ref);
- Resolve variable IDs → names for readability:
const allVars = await figma.variables.getLocalVariablesAsync();const byId = Object.fromEntries(allVars.map(v => [v.id, v.name]));// Map each boundVariables entry id → name via byId
- Apply the same bindings to the new component’s equivalent nodes (by name / by position).
Verify
Section titled “Verify”- New component’s
boundVariablesstructure matches reference’s - Screenshot both → visual parity across mode switches (GM ↔ KM ↔ dark)
Failure modes
Section titled “Failure modes”| Symptom | Cause | Recovery |
|---|---|---|
boundVariables empty on reference | Reference uses hardcoded hex (anti-pattern figma-naming-6-binding#01) | Don’t copy the hex - look up correct variable via variable-selection.md |
Reference binds to DELETED_* variable ID | Source variable was deleted; binding stale | See Workflow 4 (DELETED cleanup) |
| Binding copies but color renders wrong | Reference was in different mode (KM) when variable bound | Test new component in both modes; may need mode-switch before applying binding |
Workflow 3 - Audit GM/KM mode leaks in a component
Section titled “Workflow 3 - Audit GM/KM mode leaks in a component”When to use: QA’ing a component for mode-switch integrity. Required before declaring component ready (Q-gate F6 in FIGMA-AI.md §5.1).
Precondition
Section titled “Precondition”- Component has
boundVariablesset on child nodes - figma-console-mcp connected
- Collect all bound variable IDs in the component (recurse into children):
const ids = new Set();const recurse = (node) => {for (const prop in (node.boundVariables || {})) {const binding = node.boundVariables[prop];if (Array.isArray(binding)) binding.forEach(b => ids.add(b.id));else if (binding && binding.id) ids.add(binding.id);}if (node.children) node.children.forEach(recurse);};recurse(component);
- Inspect each variable’s
valuesByMode:const allVars = await figma.variables.getLocalVariablesAsync();const mixed = [];for (const id of ids) {const v = allVars.find(x => x.id === id);if (!v) continue;// If variable only has GM mode value but component is in KM collection → leakconst modes = Object.keys(v.valuesByMode);if (modes.length === 1 && !modes.includes('KM')) mixed.push(v.name);} - Check collection consistency: ALL variables in a single component SHOULD come from the same variable collection (DOL Design System V2). Cross-collection mixing = leak.
Verify
Section titled “Verify”- Switching Figma mode from GM → KM → GM: color values change predictably and revert identically
- No
DELETED_*references (see Workflow 4) - All variable IDs resolve to same
variableCollectionId
Failure modes
Section titled “Failure modes”| Symptom | Cause | Recovery |
|---|---|---|
| Variable has no KM value | Collection missing KM mode entry | Flag to DS team; don’t hand-add KM value to individual node |
| Variables from multiple collections in one component | Historical import drift | Re-bind using current DS V2 collection variables |
valuesByMode has unexpected mode IDs | Stale mode references | figma.variables.getVariableCollectionByIdAsync to inspect collection structure |
Workflow 4 - Clean up DELETED_* references
Section titled “Workflow 4 - Clean up DELETED_* references”When to use: component shows DELETED_<id> in boundVariables (source variable was deleted from DS).
Precondition
Section titled “Precondition”- Figma MCP connected
- Know which variable name should replace the deleted one (consult
variable-registry.md)
- Identify deleted bindings:
const deleted = [];const recurse = (node) => {for (const prop in (node.boundVariables || {})) {const binding = node.boundVariables[prop];const ids = Array.isArray(binding) ? binding.map(b => b.id) : [binding?.id];ids.filter(Boolean).forEach(id => {if (id.startsWith('DELETED_')) {deleted.push({ node: node.name, nodeId: node.id, prop, id });}});}if (node.children) node.children.forEach(recurse);};recurse(figma.currentPage);
- For each
DELETED_*, identify the intended replacement:- Check git history of DS for the deleted variable name (if possible)
- OR infer from component context (this node is styled as error text → should be
text-color-danger-primary) - OR ask user if ambiguous
- Rebind to the new variable per Workflow 1
- Re-read
boundVariables- should no longer containDELETED_*
Verify
Section titled “Verify”grep DELETED_in component’s serialized boundVariables → empty- Component renders correctly in all modes
- Q-gate F7 (FIGMA-AI §5.1) now passes
Failure modes
Section titled “Failure modes”| Symptom | Cause | Recovery |
|---|---|---|
| Can’t identify replacement | Deleted variable has no clear analogue | Ask user; worst case, unbind property and use hardcoded hex with written fallback note |
After rebind, DELETED_* still appears | Binding happened on wrong node | Re-check node path; DELETED may live on a child node with same property |
| Multiple DELETED bindings on same node | Historical churn | Batch re-bind: iterate through boundVariables map, apply replacements |
Workflow 5 - Instantiate a published component from DS library
Section titled “Workflow 5 - Instantiate a published component from DS library”When to use: building a screen that needs a standard DOL component (button, alert, input, card).
📌 Also available as Claude Code skill:
.claude/skills/figma-instantiate-component/SKILL.md(R24 extraction). Pairs withfigma-bind-variableskill - together cover the 2 primary Figma operations. Skill auto-invokes when task phrase matches “instantiate component”, “create instance”, “import this Button”, etc. This workflow remains canonical reference.
Precondition
Section titled “Precondition”- DS-Token component library is published in Figma (or you’re working in the DS file directly)
component-registry.mdis current (re-sync viatools/resync-figma.mdif DS changed)
Preferred path - CLI lookup (R18+)
Section titled “Preferred path - CLI lookup (R18+)”# 1. Find component key by name:python3 ../tools/lookup.py component "Button/Primary"
# 2. Get full variant property schema (only if you need to set variants):python3 ../tools/lookup.py variants ".Button icon"Then instantiate:
const key = '783b9c782d5b6dd44d920768ffa84c2d7f9ffe6a'; // from lookup.py outputconst imported = await figma.importComponentByKeyAsync(key);const instance = imported.createInstance();figma.currentPage.appendChild(instance);instance.x = 100;instance.y = 100;instance.setProperties({ Size: 'xl', // from `lookup.py variants` output Shape: 'Square',});Alternative - read components/.md directly
Section titled “Alternative - read components/.md directly”If browsing all components on a page (e.g., “show me all Modal variants”):
- Open
../components/<page-slug>.md(e.g.,components/modal.md) - File ≤300 lines; contains full variant property tables
- Page slugs listed in
component-registry.mdslim index
Fallback path - runtime library search
Section titled “Fallback path - runtime library search”If registry stale or component name unclear:
- Discover via MCP:
const libs = await figma.teamLibrary.getAvailableLibrariesAsync();const target = libs.flatMap(l => l.components).find(c => c.name.includes('Button'));const key = target.key;
- Continue from “instantiate” above.
- After task: flag to update registry per
tools/resync-figma.md.
Verify
Section titled “Verify”instance.type === 'INSTANCE'instance.mainComponent.key === key(matches registry row)- Component renders at expected position with correct variant
- Variant properties set without silent failure (check
instance.componentProperties)
Failure modes
Section titled “Failure modes”| Symptom | Cause | Recovery |
|---|---|---|
target is undefined in library search | Component name mismatch or library not published | Verify name in published library; check user has DS V2 library enabled in file |
importComponentByKeyAsync throws | Component key stale or library broken | Ask user to re-publish library; check component wasn’t deleted |
| Variant property set fails | Property name case-sensitive or wrong type | Read instance.componentProperties to see available properties + types |
| Instance looks wrong vs expected | Mode mismatch (file in KM, component designed in GM) | Check file mode; switch temporarily to match component origin if needed |
Meta - when any workflow’s MCP access fails
Section titled “Meta - when any workflow’s MCP access fails”If figma-console-mcp is disconnected or rate-limited:
- Don’t retry blindly - check connection status first
- Fall back to: ask user to provide the node ID / component key / variable name manually
- Document the failure in
ai-memory/queue/errors.jsonlvia /reflect
Related
Section titled “Related”naming-convention.md- 4-category rule + intensity levelsvariable-registry.md- all 838 AI-facing tokens with resolved valuesvariable-selection.md- intent → variable decision tree../anti-pattern-registry.md-figma-naming-6-bindingscope (5 binding anti-patterns)../ai-entry/FIGMA-AI.md§5.1 - F1-F7 quantified gates (apply these after any workflow completes)- Sessional lessons:
ai-memory/lessons/figma-*.md(MCP performance, API patterns, screenshot hygiene)