figma-code-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +133 -0
  2. package/bin/install.js +328 -0
  3. package/knowledge/README.md +62 -0
  4. package/knowledge/css-strategy.md +973 -0
  5. package/knowledge/design-to-code-assets.md +855 -0
  6. package/knowledge/design-to-code-layout.md +929 -0
  7. package/knowledge/design-to-code-semantic.md +1085 -0
  8. package/knowledge/design-to-code-typography.md +1003 -0
  9. package/knowledge/design-to-code-visual.md +1145 -0
  10. package/knowledge/design-tokens-variables.md +1261 -0
  11. package/knowledge/design-tokens.md +960 -0
  12. package/knowledge/figma-api-devmode.md +894 -0
  13. package/knowledge/figma-api-plugin.md +920 -0
  14. package/knowledge/figma-api-rest.md +742 -0
  15. package/knowledge/figma-api-variables.md +848 -0
  16. package/knowledge/figma-api-webhooks.md +876 -0
  17. package/knowledge/payload-blocks.md +1184 -0
  18. package/knowledge/payload-figma-mapping.md +1210 -0
  19. package/knowledge/payload-visual-builder.md +1004 -0
  20. package/knowledge/plugin-architecture.md +1176 -0
  21. package/knowledge/plugin-best-practices.md +1206 -0
  22. package/knowledge/plugin-codegen.md +1313 -0
  23. package/package.json +31 -0
  24. package/skills/README.md +103 -0
  25. package/skills/audit-plugin/SKILL.md +244 -0
  26. package/skills/build-codegen-plugin/SKILL.md +279 -0
  27. package/skills/build-importer/SKILL.md +320 -0
  28. package/skills/build-plugin/SKILL.md +199 -0
  29. package/skills/build-token-pipeline/SKILL.md +363 -0
  30. package/skills/ref-html/SKILL.md +290 -0
  31. package/skills/ref-layout/SKILL.md +150 -0
  32. package/skills/ref-payload-block/SKILL.md +415 -0
  33. package/skills/ref-react/SKILL.md +222 -0
  34. package/skills/ref-tokens/SKILL.md +347 -0
@@ -0,0 +1,848 @@
1
+ # Figma Variables API Reference
2
+
3
+ ## Purpose
4
+
5
+ Authoritative reference for the Figma Variables REST API covering access requirements, endpoints, the variable data model (Variable, VariableCollection, modes, scopes), variable resolution mechanics, multi-mode theming patterns, and practical token extraction strategies. This is the design tokens foundation that all token-related modules depend on.
6
+
7
+ ## When to Use
8
+
9
+ Reference this module when you need to:
10
+
11
+ - Extract design tokens from Figma files via the Variables API
12
+ - Understand variable collections, modes, and the `valuesByMode` structure
13
+ - Resolve variable aliases and bound variables on nodes
14
+ - Implement multi-mode theming (light/dark, brand variants)
15
+ - Build token dictionaries from Figma Variables for CSS/Tailwind/platform export
16
+ - Determine access requirements for Variables API endpoints
17
+
18
+ ---
19
+
20
+ ## Content
21
+
22
+ ### Access Requirements
23
+
24
+ The Variables REST API is restricted to **full members of Enterprise organizations**. Guests and members of non-Enterprise plans cannot access these endpoints.
25
+
26
+ | Requirement | Details |
27
+ |------------|---------|
28
+ | **Figma plan** | Enterprise |
29
+ | **Seat type** | Full member (not guest) |
30
+ | **Read scope** | `file_variables:read` |
31
+ | **Write scope** | `file_variables:write` |
32
+ | **Read permission** | View access to the file |
33
+ | **Write permission** | Edit access to the file |
34
+
35
+ > **Important:** If your organization is not on an Enterprise plan, you must extract tokens by traversing the file tree and reading style properties from nodes. See `figma-api-rest.md` for file traversal patterns.
36
+
37
+ ---
38
+
39
+ ### Endpoints
40
+
41
+ #### GET /v1/files/:file_key/variables/local
42
+
43
+ Retrieves all local variables and collections created in the file, plus any remote variables consumed by the file.
44
+
45
+ **Scope required:** `file_variables:read`
46
+ **Rate limit tier:** Tier 2
47
+
48
+ ```bash
49
+ curl -H "X-Figma-Token: YOUR_FIGMA_TOKEN" \
50
+ "https://api.figma.com/v1/files/FILE_KEY/variables/local"
51
+ ```
52
+
53
+ **Response shape:**
54
+
55
+ ```ts
56
+ interface GetLocalVariablesResponse {
57
+ status: number;
58
+ error: boolean;
59
+ meta: {
60
+ variables: Record<string, Variable>;
61
+ variableCollections: Record<string, VariableCollection>;
62
+ };
63
+ }
64
+ ```
65
+
66
+ This endpoint returns:
67
+ - All variables defined locally in the file
68
+ - All remote (external library) variables that are referenced/used in the file
69
+ - All variable collections (local and remote)
70
+
71
+ Use the `remote` boolean field on each variable/collection to distinguish local from external.
72
+
73
+ #### GET /v1/files/:file_key/variables/published
74
+
75
+ Retrieves variables that have been published from the specified file (for library consumers).
76
+
77
+ **Scope required:** `file_variables:read`
78
+ **Rate limit tier:** Tier 2
79
+
80
+ ```bash
81
+ curl -H "X-Figma-Token: YOUR_FIGMA_TOKEN" \
82
+ "https://api.figma.com/v1/files/FILE_KEY/variables/published"
83
+ ```
84
+
85
+ **Key differences from `/local`:**
86
+
87
+ - Returns only published variables (not local-only or unpublished)
88
+ - Each variable and collection includes a `subscribed_id` field for consumer tracking
89
+ - Mode details are omitted from the response
90
+
91
+ Use this endpoint when building tooling for library consumers who need to reference published tokens.
92
+
93
+ #### POST /v1/files/:file_key/variables
94
+
95
+ Bulk create, update, and delete variables, collections, and modes.
96
+
97
+ **Scope required:** `file_variables:write`
98
+ **Rate limit tier:** Tier 3
99
+
100
+ ```bash
101
+ curl -X POST \
102
+ -H "X-Figma-Token: YOUR_FIGMA_TOKEN" \
103
+ -H "Content-Type: application/json" \
104
+ -d '{ "variableCollections": [...], "variables": [...] }' \
105
+ "https://api.figma.com/v1/files/FILE_KEY/variables"
106
+ ```
107
+
108
+ **Request body arrays (processed in order):**
109
+
110
+ | Array | Purpose |
111
+ |-------|---------|
112
+ | `variableCollections` | Create, update, or delete collections |
113
+ | `variableModes` | Create, update, or delete modes |
114
+ | `variables` | Create, update, or delete variables |
115
+ | `variableModeValues` | Set variable values per mode |
116
+
117
+ **Constraints:**
118
+
119
+ | Constraint | Limit |
120
+ |-----------|-------|
121
+ | Max modes per collection | 40 |
122
+ | Max variables per collection | 5,000 |
123
+ | Max request payload | 4 MB |
124
+ | Mode name length | 40 characters |
125
+ | Variable name restrictions | Cannot contain `.`, `{`, `}` |
126
+ | Variable name uniqueness | Must be unique within collection |
127
+
128
+ **Atomicity:** All changes in a single POST request succeed or fail together. Partial application does not occur.
129
+
130
+ **Temporary IDs:** When creating new objects, you can use temporary IDs (prefixed with any string) to reference objects created in the same request. For example, create a collection and immediately add variables to it.
131
+
132
+ > **Important:** Variables created or updated via the REST API must be **published** before they become accessible in other files. The API does not auto-publish.
133
+
134
+ ---
135
+
136
+ ### Variable Data Model
137
+
138
+ #### Variable
139
+
140
+ A single design token that defines values for each mode in its parent collection.
141
+
142
+ ```ts
143
+ interface Variable {
144
+ /** Unique identifier within the file */
145
+ id: string;
146
+
147
+ /** Variable name (e.g., "primary/500", "spacing/md") */
148
+ name: string;
149
+
150
+ /** Stable key for cross-file references */
151
+ key: string;
152
+
153
+ /** Parent collection ID */
154
+ variableCollectionId: string;
155
+
156
+ /** Resolved type of this variable's values */
157
+ resolvedType: 'BOOLEAN' | 'FLOAT' | 'STRING' | 'COLOR';
158
+
159
+ /** Values indexed by mode ID */
160
+ valuesByMode: Record<string, VariableValue>;
161
+
162
+ /** Whether this variable comes from an external library */
163
+ remote: boolean;
164
+
165
+ /** Human-readable description */
166
+ description: string;
167
+
168
+ /** If true, excluded from library publishing */
169
+ hiddenFromPublishing: boolean;
170
+
171
+ /** Controls which UI pickers show this variable */
172
+ scopes: VariableScope[];
173
+
174
+ /** Platform-specific code syntax for Dev Mode */
175
+ codeSyntax: VariableCodeSyntax;
176
+
177
+ /**
178
+ * True if the variable was deleted but is still referenced
179
+ * by other variables or nodes in the file
180
+ */
181
+ deletedButReferenced?: boolean;
182
+ }
183
+ ```
184
+
185
+ #### VariableCollection
186
+
187
+ A grouping of related variables that share the same set of modes.
188
+
189
+ ```ts
190
+ interface VariableCollection {
191
+ /** Unique identifier within the file */
192
+ id: string;
193
+
194
+ /** Collection name (e.g., "Colors", "Spacing", "Typography") */
195
+ name: string;
196
+
197
+ /** Stable key for cross-file references */
198
+ key: string;
199
+
200
+ /** Modes defined in this collection: { modeId: modeName } */
201
+ modes: Record<string, string>;
202
+
203
+ /** The default mode ID (used when no explicit mode is set) */
204
+ defaultModeId: string;
205
+
206
+ /** Whether this collection comes from an external library */
207
+ remote: boolean;
208
+
209
+ /** If true, excluded from library publishing */
210
+ hiddenFromPublishing: boolean;
211
+
212
+ /** Ordered list of variable IDs in this collection */
213
+ variableIds: string[];
214
+
215
+ /** Whether this is an extended (inherited) collection */
216
+ isExtension?: boolean;
217
+
218
+ /** Parent collection ID (for extended collections) */
219
+ parentVariableCollectionId?: string;
220
+
221
+ /** Root collection ID in the extension chain */
222
+ rootVariableCollectionId?: string;
223
+
224
+ /** Variable IDs inherited from parent (extended collections) */
225
+ inheritedVariableIds?: string[];
226
+
227
+ /** Variable IDs created locally (extended collections) */
228
+ localVariableIds?: string[];
229
+
230
+ /** Override values for inherited variables */
231
+ variableOverrides?: Record<string, any>;
232
+ }
233
+ ```
234
+
235
+ #### VariableValue
236
+
237
+ A variable's value for a given mode is either a direct value or an alias to another variable.
238
+
239
+ ```ts
240
+ /** Direct values based on resolvedType */
241
+ type VariableValue =
242
+ | boolean // BOOLEAN
243
+ | number // FLOAT
244
+ | string // STRING
245
+ | RGBA // COLOR
246
+ | VariableAlias; // Reference to another variable
247
+
248
+ interface RGBA {
249
+ r: number; // 0-1
250
+ g: number; // 0-1
251
+ b: number; // 0-1
252
+ a: number; // 0-1
253
+ }
254
+
255
+ interface VariableAlias {
256
+ type: 'VARIABLE_ALIAS';
257
+ id: string; // The referenced variable's ID
258
+ }
259
+ ```
260
+
261
+ > **Note:** Color values use 0-1 range, NOT 0-255. Multiply by 255 and round when converting to hex/rgb CSS values.
262
+
263
+ #### VariableScope
264
+
265
+ Scopes control which property pickers in the Figma UI show the variable. Scopes do NOT prevent programmatic binding via the API or plugin code.
266
+
267
+ ```ts
268
+ type VariableScope =
269
+ | 'ALL_SCOPES' // Shown in all pickers (if set, no other scopes allowed)
270
+ | 'TEXT_CONTENT' // String variables: text content
271
+ | 'CORNER_RADIUS' // Float variables: border radius
272
+ | 'WIDTH_HEIGHT' // Float variables: width/height
273
+ | 'GAP' // Float variables: auto layout gap/spacing
274
+ | 'ALL_FILLS' // Color variables: all fill types
275
+ | 'FRAME_FILL' // Color variables: frame fills only
276
+ | 'SHAPE_FILL' // Color variables: shape fills only
277
+ | 'TEXT_FILL' // Color variables: text fills only
278
+ | 'STROKE_COLOR' // Color variables: stroke colors
279
+ | 'EFFECT_COLOR' // Color variables: effect colors (shadows, etc.)
280
+ | 'STROKE_FLOAT' // Float variables: stroke width
281
+ | 'EFFECT_FLOAT' // Float variables: effect values (blur, spread)
282
+ | 'OPACITY' // Float variables: opacity
283
+ | 'FONT_FAMILY' // String variables: font family
284
+ | 'FONT_STYLE' // String variables: font style
285
+ | 'FONT_WEIGHT' // Float variables: font weight
286
+ | 'FONT_SIZE' // Float variables: font size
287
+ | 'LINE_HEIGHT' // Float variables: line height
288
+ | 'LETTER_SPACING' // Float variables: letter spacing
289
+ | 'PARAGRAPH_SPACING' // Float variables: paragraph spacing
290
+ | 'PARAGRAPH_INDENT' // Float variables: paragraph indent
291
+ ;
292
+ ```
293
+
294
+ **Scope validity by resolvedType:**
295
+
296
+ | resolvedType | Valid Scopes |
297
+ |-------------|-------------|
298
+ | `BOOLEAN` | `ALL_SCOPES` |
299
+ | `FLOAT` | `ALL_SCOPES`, `TEXT_CONTENT`, `CORNER_RADIUS`, `WIDTH_HEIGHT`, `GAP`, `OPACITY`, `STROKE_FLOAT`, `EFFECT_FLOAT`, `FONT_WEIGHT`, `FONT_SIZE`, `LINE_HEIGHT`, `LETTER_SPACING`, `PARAGRAPH_SPACING`, `PARAGRAPH_INDENT` |
300
+ | `STRING` | `ALL_SCOPES`, `TEXT_CONTENT`, `FONT_FAMILY`, `FONT_STYLE` |
301
+ | `COLOR` | `ALL_SCOPES`, `ALL_FILLS`, `FRAME_FILL`, `SHAPE_FILL`, `TEXT_FILL`, `STROKE_COLOR`, `EFFECT_COLOR` |
302
+
303
+ #### VariableCodeSyntax
304
+
305
+ Platform-specific code representations displayed in Dev Mode.
306
+
307
+ ```ts
308
+ interface VariableCodeSyntax {
309
+ WEB?: string; // e.g., "var(--color-primary)"
310
+ ANDROID?: string; // e.g., "@color/primary"
311
+ iOS?: string; // e.g., "Color.primary"
312
+ }
313
+ ```
314
+
315
+ ---
316
+
317
+ ### Variable Resolution
318
+
319
+ #### Direct Values vs Aliases
320
+
321
+ A variable's value for each mode can be either a **direct value** (literal boolean, number, string, or color) or a **variable alias** (reference to another variable).
322
+
323
+ ```ts
324
+ // Direct value example (COLOR type, mode "light"):
325
+ {
326
+ "valuesByMode": {
327
+ "mode-light-id": { "r": 0.067, "g": 0.067, "b": 0.067, "a": 1 },
328
+ "mode-dark-id": { "r": 0.933, "g": 0.933, "b": 0.933, "a": 1 }
329
+ }
330
+ }
331
+
332
+ // Alias example (references another variable):
333
+ {
334
+ "valuesByMode": {
335
+ "mode-light-id": { "type": "VARIABLE_ALIAS", "id": "VariableID:1234:0" },
336
+ "mode-dark-id": { "type": "VARIABLE_ALIAS", "id": "VariableID:5678:0" }
337
+ }
338
+ }
339
+ ```
340
+
341
+ #### Resolving Alias Chains
342
+
343
+ Aliases can chain: Variable A references Variable B which references Variable C. To resolve to a final value, follow the chain:
344
+
345
+ ```ts
346
+ function resolveVariable(
347
+ variableId: string,
348
+ modeId: string,
349
+ variables: Record<string, Variable>
350
+ ): VariableValue {
351
+ const variable = variables[variableId];
352
+ if (!variable) throw new Error(`Variable ${variableId} not found`);
353
+
354
+ const value = variable.valuesByMode[modeId];
355
+ if (!value) {
356
+ // Fall back to default mode
357
+ const collection = findCollection(variable.variableCollectionId);
358
+ return variable.valuesByMode[collection.defaultModeId];
359
+ }
360
+
361
+ // If it's an alias, resolve recursively
362
+ if (typeof value === 'object' && 'type' in value && value.type === 'VARIABLE_ALIAS') {
363
+ return resolveVariable(value.id, modeId, variables);
364
+ }
365
+
366
+ return value;
367
+ }
368
+ ```
369
+
370
+ > **Note:** Figma prevents alias cycles — a variable cannot reference itself directly or through a chain.
371
+
372
+ #### Mode Resolution and Fallbacks
373
+
374
+ Each variable has values indexed by mode ID in its `valuesByMode` map. Resolution order:
375
+
376
+ 1. Look up the value for the **requested mode ID**
377
+ 2. If no value exists for that mode, use the **collection's `defaultModeId`**
378
+ 3. If the value is an alias, resolve the referenced variable using the **same mode ID**
379
+
380
+ When an alias references a variable in a **different** collection, the referenced variable resolves using its own collection's modes. Cross-collection mode mapping is not automatic.
381
+
382
+ #### Bound Variables on Nodes
383
+
384
+ Nodes in the Figma file tree indicate which variables are bound to their properties via the `boundVariables` field:
385
+
386
+ ```ts
387
+ // Example: a frame with variables bound to fills and corner radius
388
+ {
389
+ "type": "FRAME",
390
+ "name": "Card",
391
+ "boundVariables": {
392
+ "fills": [
393
+ {
394
+ "type": "VARIABLE_ALIAS",
395
+ "id": "VariableID:1234:0"
396
+ }
397
+ ],
398
+ "topLeftRadius": {
399
+ "type": "VARIABLE_ALIAS",
400
+ "id": "VariableID:5678:0"
401
+ },
402
+ "topRightRadius": {
403
+ "type": "VARIABLE_ALIAS",
404
+ "id": "VariableID:5678:0"
405
+ },
406
+ "itemSpacing": {
407
+ "type": "VARIABLE_ALIAS",
408
+ "id": "VariableID:9012:0"
409
+ }
410
+ }
411
+ }
412
+ ```
413
+
414
+ **Properties that can have bound variables include:** fills, strokes, effects, cornerRadius (and individual corners), itemSpacing, paddingLeft/Right/Top/Bottom, width, height, opacity, fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, and more.
415
+
416
+ The `boundVariables` map tells you which properties are token-driven vs hard-coded, which is essential for design-to-code token mapping.
417
+
418
+ ---
419
+
420
+ ### Multi-Mode Theming
421
+
422
+ #### Pattern: Light/Dark Mode
423
+
424
+ The most common multi-mode pattern is light and dark themes.
425
+
426
+ **Figma structure:**
427
+
428
+ ```
429
+ Collection: "Theme"
430
+ Modes: ["Light", "Dark"]
431
+ Variables:
432
+ "bg/primary" → Light: #FFFFFF, Dark: #1A1A1A
433
+ "bg/secondary" → Light: #F5F5F5, Dark: #2D2D2D
434
+ "text/primary" → Light: #1A1A1A, Dark: #FFFFFF
435
+ "text/secondary" → Light: #666666, Dark: #A0A0A0
436
+ "border/default" → Light: #E0E0E0, Dark: #404040
437
+ ```
438
+
439
+ **CSS output (class-based switching):**
440
+
441
+ ```css
442
+ :root,
443
+ [data-theme="light"] {
444
+ --bg-primary: #ffffff;
445
+ --bg-secondary: #f5f5f5;
446
+ --text-primary: #1a1a1a;
447
+ --text-secondary: #666666;
448
+ --border-default: #e0e0e0;
449
+ }
450
+
451
+ [data-theme="dark"] {
452
+ --bg-primary: #1a1a1a;
453
+ --bg-secondary: #2d2d2d;
454
+ --text-primary: #ffffff;
455
+ --text-secondary: #a0a0a0;
456
+ --border-default: #404040;
457
+ }
458
+ ```
459
+
460
+ **CSS output (media query):**
461
+
462
+ ```css
463
+ :root {
464
+ --bg-primary: #ffffff;
465
+ --text-primary: #1a1a1a;
466
+ }
467
+
468
+ @media (prefers-color-scheme: dark) {
469
+ :root {
470
+ --bg-primary: #1a1a1a;
471
+ --text-primary: #ffffff;
472
+ }
473
+ }
474
+ ```
475
+
476
+ #### Pattern: Brand Theming
477
+
478
+ Multiple brands sharing a common token structure.
479
+
480
+ **Figma structure:**
481
+
482
+ ```
483
+ Collection: "Brand"
484
+ Modes: ["Brand A", "Brand B", "Brand C"]
485
+ Variables:
486
+ "color/primary" → A: #0066FF, B: #FF3366, C: #00CC88
487
+ "color/secondary" → A: #003399, B: #CC0033, C: #009966
488
+ "font/heading" → A: "Inter", B: "Poppins", C: "DM Sans"
489
+ ```
490
+
491
+ **CSS output (class-based):**
492
+
493
+ ```css
494
+ .brand-a {
495
+ --color-primary: #0066ff;
496
+ --color-secondary: #003399;
497
+ --font-heading: 'Inter', sans-serif;
498
+ }
499
+
500
+ .brand-b {
501
+ --color-primary: #ff3366;
502
+ --color-secondary: #cc0033;
503
+ --font-heading: 'Poppins', sans-serif;
504
+ }
505
+
506
+ .brand-c {
507
+ --color-primary: #00cc88;
508
+ --color-secondary: #009966;
509
+ --font-heading: 'DM Sans', sans-serif;
510
+ }
511
+ ```
512
+
513
+ #### Pattern: Layered Modes (Theme + Density)
514
+
515
+ Multiple collections with independent modes that compose together.
516
+
517
+ **Figma structure:**
518
+
519
+ ```
520
+ Collection: "Theme"
521
+ Modes: ["Light", "Dark"]
522
+ Variables: color tokens...
523
+
524
+ Collection: "Density"
525
+ Modes: ["Compact", "Default", "Comfortable"]
526
+ Variables: spacing tokens...
527
+ ```
528
+
529
+ In CSS, these compose naturally because each collection maps to independent custom properties:
530
+
531
+ ```css
532
+ /* Theme layer */
533
+ [data-theme="light"] { --bg: #fff; --text: #1a1a1a; }
534
+ [data-theme="dark"] { --bg: #1a1a1a; --text: #fff; }
535
+
536
+ /* Density layer (independent) */
537
+ [data-density="compact"] { --spacing-md: 8px; --spacing-lg: 12px; }
538
+ [data-density="default"] { --spacing-md: 16px; --spacing-lg: 24px; }
539
+ [data-density="comfortable"] { --spacing-md: 24px; --spacing-lg: 32px; }
540
+ ```
541
+
542
+ ---
543
+
544
+ ### Practical Patterns
545
+
546
+ #### Extracting All Color Tokens
547
+
548
+ ```ts
549
+ interface ColorToken {
550
+ name: string;
551
+ variableId: string;
552
+ collection: string;
553
+ values: Record<string, { hex: string; rgba: RGBA }>;
554
+ }
555
+
556
+ async function extractColorTokens(fileKey: string): Promise<ColorToken[]> {
557
+ const res = await fetch(
558
+ `https://api.figma.com/v1/files/${fileKey}/variables/local`,
559
+ { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
560
+ );
561
+ const { meta } = await res.json();
562
+ const tokens: ColorToken[] = [];
563
+
564
+ for (const variable of Object.values(meta.variables) as Variable[]) {
565
+ if (variable.resolvedType !== 'COLOR') continue;
566
+ if (variable.remote) continue; // Skip imported variables
567
+
568
+ const collection = meta.variableCollections[variable.variableCollectionId];
569
+
570
+ const values: Record<string, { hex: string; rgba: RGBA }> = {};
571
+ for (const [modeId, value] of Object.entries(variable.valuesByMode)) {
572
+ const resolved = resolveToColor(value as VariableValue, modeId, meta.variables);
573
+ if (resolved) {
574
+ const modeName = collection.modes[modeId] || modeId;
575
+ values[modeName] = {
576
+ hex: rgbaToHex(resolved),
577
+ rgba: resolved,
578
+ };
579
+ }
580
+ }
581
+
582
+ tokens.push({
583
+ name: variable.name,
584
+ variableId: variable.id,
585
+ collection: collection.name,
586
+ values,
587
+ });
588
+ }
589
+
590
+ return tokens;
591
+ }
592
+
593
+ function rgbaToHex(c: RGBA): string {
594
+ const toHex = (v: number) => Math.round(v * 255).toString(16).padStart(2, '0');
595
+ const hex = `#${toHex(c.r)}${toHex(c.g)}${toHex(c.b)}`;
596
+ return c.a < 1 ? `${hex}${toHex(c.a)}` : hex;
597
+ }
598
+ ```
599
+
600
+ #### Building a Complete Token Dictionary
601
+
602
+ ```ts
603
+ interface TokenDictionary {
604
+ colors: Record<string, Record<string, string>>; // name → { modeName: hex }
605
+ numbers: Record<string, Record<string, number>>; // name → { modeName: value }
606
+ strings: Record<string, Record<string, string>>; // name → { modeName: value }
607
+ booleans: Record<string, Record<string, boolean>>; // name → { modeName: value }
608
+ }
609
+
610
+ async function buildTokenDictionary(fileKey: string): Promise<TokenDictionary> {
611
+ const res = await fetch(
612
+ `https://api.figma.com/v1/files/${fileKey}/variables/local`,
613
+ { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
614
+ );
615
+ const { meta } = await res.json();
616
+
617
+ const dict: TokenDictionary = { colors: {}, numbers: {}, strings: {}, booleans: {} };
618
+
619
+ for (const variable of Object.values(meta.variables) as Variable[]) {
620
+ if (variable.remote) continue;
621
+ if (variable.hiddenFromPublishing) continue;
622
+
623
+ const collection = meta.variableCollections[variable.variableCollectionId];
624
+ const tokenName = `${collection.name}/${variable.name}`;
625
+
626
+ for (const [modeId, rawValue] of Object.entries(variable.valuesByMode)) {
627
+ const modeName = collection.modes[modeId] || modeId;
628
+ const value = resolveValue(rawValue as VariableValue, modeId, meta.variables);
629
+
630
+ switch (variable.resolvedType) {
631
+ case 'COLOR':
632
+ dict.colors[tokenName] ??= {};
633
+ dict.colors[tokenName][modeName] = rgbaToHex(value as RGBA);
634
+ break;
635
+ case 'FLOAT':
636
+ dict.numbers[tokenName] ??= {};
637
+ dict.numbers[tokenName][modeName] = value as number;
638
+ break;
639
+ case 'STRING':
640
+ dict.strings[tokenName] ??= {};
641
+ dict.strings[tokenName][modeName] = value as string;
642
+ break;
643
+ case 'BOOLEAN':
644
+ dict.booleans[tokenName] ??= {};
645
+ dict.booleans[tokenName][modeName] = value as boolean;
646
+ break;
647
+ }
648
+ }
649
+ }
650
+
651
+ return dict;
652
+ }
653
+ ```
654
+
655
+ #### Detecting Which Variables Are Actually Used
656
+
657
+ Combine file tree traversal with the Variables API to find which tokens are actively bound to design elements:
658
+
659
+ ```ts
660
+ async function findUsedVariables(fileKey: string): Promise<Set<string>> {
661
+ // 1. Fetch the file tree
662
+ const fileRes = await fetch(
663
+ `https://api.figma.com/v1/files/${fileKey}`,
664
+ { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
665
+ );
666
+ const file = await fileRes.json();
667
+
668
+ // 2. Walk the tree and collect all bound variable IDs
669
+ const usedIds = new Set<string>();
670
+
671
+ function walk(node: any) {
672
+ if (node.boundVariables) {
673
+ for (const bindings of Object.values(node.boundVariables)) {
674
+ if (Array.isArray(bindings)) {
675
+ for (const b of bindings) {
676
+ if (b && typeof b === 'object' && b.type === 'VARIABLE_ALIAS') {
677
+ usedIds.add(b.id);
678
+ }
679
+ }
680
+ } else if (bindings && typeof bindings === 'object') {
681
+ const b = bindings as any;
682
+ if (b.type === 'VARIABLE_ALIAS') {
683
+ usedIds.add(b.id);
684
+ }
685
+ }
686
+ }
687
+ }
688
+ if (node.children) {
689
+ node.children.forEach(walk);
690
+ }
691
+ }
692
+
693
+ walk(file.document);
694
+ return usedIds;
695
+ }
696
+ ```
697
+
698
+ #### Generating CSS Custom Properties from Variables
699
+
700
+ ```ts
701
+ async function generateCSSVariables(fileKey: string): Promise<string> {
702
+ const res = await fetch(
703
+ `https://api.figma.com/v1/files/${fileKey}/variables/local`,
704
+ { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
705
+ );
706
+ const { meta } = await res.json();
707
+
708
+ const output: string[] = [];
709
+
710
+ // Group variables by collection
711
+ for (const collection of Object.values(meta.variableCollections) as VariableCollection[]) {
712
+ if (collection.remote) continue;
713
+
714
+ const collectionVars = collection.variableIds
715
+ .map(id => meta.variables[id])
716
+ .filter((v): v is Variable => v != null && !v.hiddenFromPublishing);
717
+
718
+ // Generate CSS for each mode
719
+ const modeEntries = Object.entries(collection.modes);
720
+
721
+ for (const [modeId, modeName] of modeEntries) {
722
+ const isDefault = modeId === collection.defaultModeId;
723
+ const selector = isDefault
724
+ ? ':root'
725
+ : `[data-${slugify(collection.name)}="${slugify(modeName)}"]`;
726
+
727
+ output.push(`${selector} {`);
728
+
729
+ for (const variable of collectionVars) {
730
+ const value = variable.valuesByMode[modeId];
731
+ if (!value) continue;
732
+
733
+ const resolved = resolveValue(value, modeId, meta.variables);
734
+ const cssName = `--${tokenToCSSName(variable.name)}`;
735
+ const cssValue = formatCSSValue(resolved, variable.resolvedType);
736
+
737
+ output.push(` ${cssName}: ${cssValue};`);
738
+ }
739
+
740
+ output.push('}');
741
+ output.push('');
742
+ }
743
+ }
744
+
745
+ return output.join('\n');
746
+ }
747
+
748
+ function tokenToCSSName(name: string): string {
749
+ return name
750
+ .replace(/\//g, '-')
751
+ .replace(/\s+/g, '-')
752
+ .replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
753
+ .replace(/^-/, '')
754
+ .toLowerCase();
755
+ }
756
+
757
+ function slugify(str: string): string {
758
+ return str.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
759
+ }
760
+
761
+ function formatCSSValue(value: any, type: string): string {
762
+ switch (type) {
763
+ case 'COLOR': {
764
+ const c = value as RGBA;
765
+ if (c.a < 1) {
766
+ return `rgba(${Math.round(c.r * 255)}, ${Math.round(c.g * 255)}, ${Math.round(c.b * 255)}, ${c.a})`;
767
+ }
768
+ return rgbaToHex(c);
769
+ }
770
+ case 'FLOAT':
771
+ return `${value}px`;
772
+ case 'STRING':
773
+ return `'${value}'`;
774
+ case 'BOOLEAN':
775
+ return value ? '1' : '0';
776
+ default:
777
+ return String(value);
778
+ }
779
+ }
780
+ ```
781
+
782
+ #### Platform-Agnostic Token Export
783
+
784
+ The `codeSyntax` field on variables provides platform-specific token names for Dev Mode:
785
+
786
+ ```ts
787
+ // Example: use codeSyntax for platform-aware export
788
+ function exportForPlatform(
789
+ variable: Variable,
790
+ platform: 'WEB' | 'ANDROID' | 'iOS'
791
+ ): string | null {
792
+ // Prefer explicit codeSyntax if set by designers
793
+ if (variable.codeSyntax[platform]) {
794
+ return variable.codeSyntax[platform];
795
+ }
796
+
797
+ // Fall back to auto-generated name
798
+ switch (platform) {
799
+ case 'WEB':
800
+ return `var(--${tokenToCSSName(variable.name)})`;
801
+ case 'ANDROID':
802
+ return `@dimen/${variable.name.replace(/\//g, '_').toLowerCase()}`;
803
+ case 'iOS':
804
+ return variable.name.replace(/\//g, '.').replace(/\s+/g, '');
805
+ default:
806
+ return null;
807
+ }
808
+ }
809
+ ```
810
+
811
+ ---
812
+
813
+ ### Token Extraction Priority
814
+
815
+ When extracting design tokens from a Figma file, use this priority order:
816
+
817
+ 1. **Variables API** (this module) — Best structured source. Includes modes, scopes, aliases, and explicit token definitions. Requires Enterprise plan.
818
+
819
+ 2. **Published styles** — Use `/files/:key/styles` for published library styles. Limited to published content only. See `figma-api-rest.md`.
820
+
821
+ 3. **File tree traversal + inference** — Walk the node tree, read fill/stroke/effect properties, and derive tokens by frequency analysis (threshold-based promotion). Works on all plans but requires heuristics.
822
+
823
+ 4. **Plugin API** — For local styles and variables when REST is plan-limited. Requires running a Figma plugin in-context. See `figma-api-plugin.md`.
824
+
825
+ ---
826
+
827
+ ### Error Handling
828
+
829
+ | Status | Meaning | Action |
830
+ |--------|---------|--------|
831
+ | `400` | Invalid parameter (bad variable name, scope mismatch) | Check request body against constraints |
832
+ | `401` | Authentication failed | Verify token and `file_variables:read/write` scope |
833
+ | `403` | Insufficient permissions (non-Enterprise or guest) | Confirm Enterprise plan, full member status, and file access |
834
+ | `404` | File not found | Verify file key |
835
+ | `413` | Payload exceeds 4 MB | Split POST request into smaller batches |
836
+ | `429` | Rate limited | Use `Retry-After` header. See `figma-api-rest.md` for backoff strategy |
837
+
838
+ ---
839
+
840
+ ## Cross-References
841
+
842
+ - **`figma-api-rest.md`** — Core REST API reference (authentication, file endpoints, rate limits, node structure)
843
+ - **`figma-api-plugin.md`** — Plugin API alternative for variables access on non-Enterprise plans
844
+ - **`figma-api-webhooks.md`** — Webhooks v2 LIBRARY_PUBLISH event for reacting to published variable changes
845
+ - **`design-tokens.md`** — Token extraction strategies, CSS variable naming, threshold-based promotion
846
+ - **`design-tokens-variables.md`** — Deep dive on Figma Variables patterns (modes, fallback chains, scope usage)
847
+ - **`css-strategy.md`** — Layered CSS approach for consuming design tokens
848
+ - **`design-to-code-visual.md`** — Visual property extraction that consumes bound variable data