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,960 @@
1
+ # Design Token Extraction & Generation
2
+
3
+ ## Purpose
4
+
5
+ Authoritative reference for extracting design tokens from Figma designs and rendering them as CSS Custom Properties, SCSS variables, or Tailwind theme configuration. Documents the complete token pipeline: traversing an ExtractedNode tree to collect color, spacing, typography, and effect values; promoting repeated values to tokens via a threshold system; assigning semantic names using HSL analysis and scale detection; building reverse lookup maps for CSS variable substitution; and rendering the final token output in multiple formats.
6
+
7
+ ## When to Use
8
+
9
+ Reference this module when you need to:
10
+
11
+ - Extract design tokens from a Figma node tree (colors, spacing, typography, effects)
12
+ - Understand the threshold-based promotion system that decides which values become CSS variables
13
+ - Apply semantic color naming based on HSL hue/saturation classification
14
+ - Generate spacing scale names from detected base units (4px, 8px grids)
15
+ - Build a token lookup map for substituting raw values with `var()` references during CSS generation
16
+ - Render tokens as CSS Custom Properties in a `:root` block with mode-aware media queries
17
+ - Render tokens as SCSS `$variable` declarations
18
+ - Map token categories to a Tailwind theme configuration
19
+ - Understand the priority system: Figma Variables override auto-detected tokens
20
+ - Handle token naming conflicts and deduplication
21
+
22
+ ---
23
+
24
+ ## Content
25
+
26
+ ### 1. Token Extraction Pipeline
27
+
28
+ The token pipeline follows a four-stage process: Collect, Promote, Name, and Render.
29
+
30
+ ```
31
+ Stage 1: COLLECT
32
+ Traverse ExtractedNode tree
33
+ Collect raw values from fills, strokes, effects, layout, text
34
+ Track usage count per unique value
35
+ Collect Figma Variable bindings separately
36
+
37
+ Stage 2: PROMOTE
38
+ Apply threshold filter (default: usageCount >= 2)
39
+ Single-use values stay inline in component CSS
40
+ Multi-use values become CSS variables
41
+ Figma Variables always promoted (explicit bindings)
42
+
43
+ Stage 3: NAME
44
+ Assign semantic names to promoted tokens
45
+ Colors: HSL-based classification → primary, neutral-800, success
46
+ Spacing: Base unit detection → spacing-1, spacing-4
47
+ Typography: Type scale + role → text-lg, font-primary, font-bold
48
+ Effects: Size scale → shadow-md, radius-lg
49
+
50
+ Stage 4: RENDER
51
+ Generate CSS Custom Properties (:root block)
52
+ Generate SCSS $variables (_variables.scss)
53
+ Generate Tailwind theme config (theme.extend)
54
+ Generate TokenLookup for CSS variable substitution
55
+ ```
56
+
57
+ #### Entry Point: `collectAllTokens`
58
+
59
+ The pipeline starts with `collectAllTokens(node)`, which calls four specialized collectors and one variable collector:
60
+
61
+ ```
62
+ collectAllTokens(node) →
63
+ ├── collectColors(node) → ColorToken[]
64
+ ├── collectSpacing(node) → SpacingToken[]
65
+ ├── collectTypography(node) → TypographyTokens
66
+ ├── collectEffects(node) → EffectTokens
67
+ └── collectFigmaVariables(node) → FigmaVariableToken[]
68
+ ```
69
+
70
+ Each collector traverses the entire node tree recursively, extracting values relevant to its domain and tracking usage counts.
71
+
72
+ ---
73
+
74
+ ### 2. Token Types
75
+
76
+ All tokens share a common shape: a name (CSS variable name), a value (raw CSS value), and a usage count (number of occurrences in the design).
77
+
78
+ #### Type Definitions
79
+
80
+ | Token Type | Name Pattern | Value Type | Additional Fields |
81
+ |-----------|-------------|-----------|-------------------|
82
+ | `ColorToken` | `--color-primary`, `--color-neutral-800` | hex string or rgba() | `semantic` classification |
83
+ | `SpacingToken` | `--spacing-1`, `--spacing-4` | number (pixels) | `context` (gap, padding, margin) |
84
+ | `FontFamilyToken` | `--font-primary`, `--font-mono` | string (family name) | -- |
85
+ | `FontSizeToken` | `--text-base`, `--text-lg` | number (pixels) | -- |
86
+ | `FontWeightToken` | `--font-bold`, `--font-medium` | number (100-900) | -- |
87
+ | `ShadowToken` | `--shadow-sm`, `--shadow-md` | string (CSS box-shadow) | -- |
88
+ | `RadiusToken` | `--radius-sm`, `--radius-lg` | number (pixels) or string | -- |
89
+ | `FigmaVariableToken` | `--color-link-default` | string (CSS value) | `figmaName`, `isLocal`, `collectionId`, `modeValues` |
90
+
91
+ #### Container Types
92
+
93
+ Tokens are grouped into domain-specific containers:
94
+
95
+ ```
96
+ DesignTokens
97
+ ├── colors: ColorToken[]
98
+ ├── spacing: SpacingToken[]
99
+ ├── typography: TypographyTokens
100
+ │ ├── families: FontFamilyToken[]
101
+ │ ├── sizes: FontSizeToken[]
102
+ │ └── weights: FontWeightToken[]
103
+ ├── effects: EffectTokens
104
+ │ ├── shadows: ShadowToken[]
105
+ │ └── radii: RadiusToken[]
106
+ ├── figmaVariables?: FigmaVariableToken[]
107
+ └── variableCollections?: VariableCollection[]
108
+ ```
109
+
110
+ ---
111
+
112
+ ### 3. Color Token Extraction
113
+
114
+ Colors are extracted from three sources within each node: solid fills, strokes, and shadow effects. Gradient stop colors are skipped in the current implementation.
115
+
116
+ #### Collection Sources
117
+
118
+ | Source | Extraction | CSS Property Origin |
119
+ |--------|-----------|-------------------|
120
+ | Solid fills | `fill.color` (when `fill.type === 'SOLID'`) | `background-color`, `color` |
121
+ | Strokes | `stroke.color` | `border-color` |
122
+ | Shadow effects | `effect.color` (DROP_SHADOW, INNER_SHADOW) | `box-shadow` |
123
+
124
+ #### Normalization
125
+
126
+ Before comparison, all color values are normalized:
127
+
128
+ - **Hex colors:** Lowercased (`#1A73E8` becomes `#1a73e8`)
129
+ - **RGBA colors:** Whitespace normalized (`rgba( 255, 128, 64, 0.5 )` becomes `rgba(255, 128, 64, 0.5)`)
130
+
131
+ Normalization is critical for accurate deduplication. Without it, `#1A73E8` and `#1a73e8` would be treated as two different colors.
132
+
133
+ #### HSL-Based Semantic Naming
134
+
135
+ After collection and sorting by usage count (most-used first), each color is assigned a semantic name based on its HSL (Hue, Saturation, Lightness) values.
136
+
137
+ **Step 1: Convert to HSL**
138
+
139
+ ```
140
+ Hex → RGB (0-255) → RGB (0-1) → HSL (h: 0-360, s: 0-100, l: 0-100)
141
+ ```
142
+
143
+ **Step 2: Classify by saturation and hue**
144
+
145
+ | HSL Condition | Semantic | Name Pattern |
146
+ |--------------|----------|-------------|
147
+ | Saturation < 10% | `neutral` | `--color-neutral-{100-900}` (by lightness) |
148
+ | Hue 0-20 or 340-360 | `error` | `--color-error` |
149
+ | Hue 30-60 | `warning` | `--color-warning` |
150
+ | Hue 90-150 | `success` | `--color-success` |
151
+ | Most-used saturated color | `primary` | `--color-primary` |
152
+ | Second most-used saturated | `secondary` | `--color-secondary` |
153
+ | Remaining saturated colors | `accent` | `--color-accent`, `--color-accent-2`, ... |
154
+
155
+ **Step 3: Neutral lightness scale**
156
+
157
+ Neutral colors (saturation < 10%) use a 100-900 scale based on lightness, inverted so that 100 is lightest and 900 is darkest:
158
+
159
+ ```
160
+ step = Math.round((1 - lightness / 100) * 8 + 1) * 100
161
+ Clamped to range [100, 900]
162
+
163
+ lightness 95% → step 100 (near white)
164
+ lightness 85% → step 200
165
+ lightness 50% → step 500 (medium gray)
166
+ lightness 15% → step 800
167
+ lightness 5% → step 900 (near black)
168
+ ```
169
+
170
+ #### Semantic Classification Logic
171
+
172
+ The classification uses this priority order:
173
+
174
+ 1. **Neutral first** -- Low saturation (< 10%) colors are always `neutral`, regardless of hue
175
+ 2. **Functional colors** -- Error (red hue), warning (yellow/orange hue), success (green hue)
176
+ 3. **Ranked by usage** -- Among remaining saturated colors, the most-used is `primary`, second is `secondary`, rest are `accent`
177
+
178
+ The functional color check happens before the usage-ranked check. This means a red color will be classified as `error` even if it is the most-used saturated color.
179
+
180
+ #### Duplicate Name Resolution
181
+
182
+ When multiple colors would receive the same name (e.g., two colors both classified as `--color-neutral-500`), a numeric suffix is appended:
183
+
184
+ ```
185
+ First: --color-neutral-500
186
+ Second: --color-neutral-500-2
187
+ Third: --color-neutral-500-3
188
+ ```
189
+
190
+ #### Example Color Collection
191
+
192
+ Given a design with these colors:
193
+
194
+ | Color | Usage | HSL | Classification | Token Name |
195
+ |-------|-------|-----|----------------|------------|
196
+ | `#1a73e8` | 12 | H:217 S:84 L:51 | primary (most-used saturated) | `--color-primary` |
197
+ | `#333333` | 28 | H:0 S:0 L:20 | neutral (s<10, l=20) | `--color-neutral-800` |
198
+ | `#f5f5f5` | 15 | H:0 S:0 L:96 | neutral (s<10, l=96) | `--color-neutral-100` |
199
+ | `#34a853` | 3 | H:137 S:52 L:43 | success (h=137, in 90-150) | `--color-success` |
200
+ | `#ea4335` | 2 | H:5 S:81 L:56 | error (h=5, in 0-20) | `--color-error` |
201
+ | `#5f6368` | 8 | H:213 S:5 L:39 | neutral (s<10, l=39) | `--color-neutral-600` |
202
+
203
+ > For how extracted colors resolve Figma Variable bindings, see `design-to-code-visual.md` Section 7.
204
+
205
+ ---
206
+
207
+ ### 4. Spacing Token Extraction
208
+
209
+ Spacing values are collected from layout properties: `gap`, `counterAxisGap`, and `padding` (all four sides). Zero values are skipped.
210
+
211
+ #### Collection Sources
212
+
213
+ | Source | Property | Context Tag |
214
+ |--------|----------|------------|
215
+ | Primary gap | `layout.gap` | `gap` |
216
+ | Counter axis gap | `layout.counterAxisGap` | `gap` |
217
+ | Padding top | `layout.padding.top` | `padding` |
218
+ | Padding right | `layout.padding.right` | `padding` |
219
+ | Padding bottom | `layout.padding.bottom` | `padding` |
220
+ | Padding left | `layout.padding.left` | `padding` |
221
+
222
+ Each spacing value is tracked with its usage count and the contexts where it appears (gap, padding, or both).
223
+
224
+ #### Base Unit Detection
225
+
226
+ The naming algorithm detects whether the collected spacing values follow a regular scale:
227
+
228
+ **Step 1:** Try common base units in order: 4, 8, 2, 5, 10
229
+
230
+ ```
231
+ For each candidate base unit:
232
+ If ALL spacing values are evenly divisible by this base → use it
233
+ ```
234
+
235
+ **Step 2:** If no common base fits, compute the GCD (Greatest Common Divisor) of all values.
236
+
237
+ **Step 3:** If a base unit is found (> 1), use scale-based naming. Otherwise, use direct-value naming.
238
+
239
+ #### Scale-Based Naming
240
+
241
+ When all values fit a base unit, tokens use step numbers:
242
+
243
+ ```
244
+ Base unit: 4px
245
+ Values: [4, 8, 12, 16, 24, 32, 48]
246
+
247
+ 4px ÷ 4 = step 1 → --spacing-1
248
+ 8px ÷ 4 = step 2 → --spacing-2
249
+ 12px ÷ 4 = step 3 → --spacing-3
250
+ 16px ÷ 4 = step 4 → --spacing-4
251
+ 24px ÷ 4 = step 6 → --spacing-6
252
+ 32px ÷ 4 = step 8 → --spacing-8
253
+ 48px ÷ 4 = step 12 → --spacing-12
254
+ ```
255
+
256
+ #### Direct-Value Naming
257
+
258
+ When values do not fit a regular scale, the raw pixel value is used:
259
+
260
+ ```
261
+ Values: [5, 11, 17, 23]
262
+ → --spacing-5, --spacing-11, --spacing-17, --spacing-23
263
+ ```
264
+
265
+ #### T-Shirt Size Scale
266
+
267
+ An alternative approach uses semantic T-shirt size names instead of numeric steps, following an 8-point grid:
268
+
269
+ | Token | Value | Base Unit Step |
270
+ |-------|-------|---------------|
271
+ | `--token-spacing-xs` | 4px | 0.5 |
272
+ | `--token-spacing-sm` | 8px | 1 |
273
+ | `--token-spacing-md` | 16px | 2 |
274
+ | `--token-spacing-lg` | 24px | 3 |
275
+ | `--token-spacing-xl` | 32px | 4 |
276
+ | `--token-spacing-2xl` | 48px | 6 |
277
+ | `--token-spacing-3xl` | 64px | 8 |
278
+
279
+ This is a manual token definition, not auto-generated. Auto-generated spacing tokens use numeric step names. Projects can rename tokens to T-shirt sizes after generation.
280
+
281
+ ---
282
+
283
+ ### 5. Typography Token Extraction
284
+
285
+ Typography tokens are collected from text nodes: font families, font sizes, and font weights. Each is tracked independently with its own usage count.
286
+
287
+ #### Font Family Token Naming
288
+
289
+ Families are sorted alphabetically, then named by role:
290
+
291
+ | Role | Detection | Token Name |
292
+ |------|-----------|------------|
293
+ | Primary | Most-used font family (by usage count) | `--font-primary` |
294
+ | Monospace | Name matches monospace patterns (mono, code, consolas, courier, etc.) | `--font-mono` |
295
+ | Secondary | First non-primary, non-mono font | `--font-secondary` |
296
+ | Others | Remaining fonts by index | `--font-family-{n}` |
297
+
298
+ **Monospace detection patterns:** `mono`, `code`, `consolas`, `courier`, `fira code`, `jetbrains`, `menlo`, `monaco`, `source code`, `ubuntu mono`, `sf mono`, `roboto mono`, `ibm plex mono`. Detection is case-insensitive substring matching.
299
+
300
+ #### Font Size Token Naming (Type Scale)
301
+
302
+ Font sizes use a T-shirt size scale anchored to a "base" size. The base is the collected size closest to 16px.
303
+
304
+ **Scale positions:**
305
+
306
+ | Position | Scale Name | Relative to Base |
307
+ |----------|-----------|-----------------|
308
+ | base - 2 | `--text-xs` | Two steps smaller |
309
+ | base - 1 | `--text-sm` | One step smaller |
310
+ | base | `--text-base` | Anchor (closest to 16px) |
311
+ | base + 1 | `--text-lg` | One step larger |
312
+ | base + 2 | `--text-xl` | Two steps larger |
313
+ | base + 3 | `--text-2xl` | Three steps larger |
314
+ | base + 4 | `--text-3xl` | Four steps larger |
315
+ | base + 5 | `--text-4xl` | Five steps larger |
316
+ | base + 6 | `--text-5xl` | Six steps larger |
317
+ | base + 7 | `--text-6xl` | Seven steps larger |
318
+
319
+ Sizes outside the scale range fall back to `--text-{px}` naming (e.g., `--text-72`).
320
+
321
+ **Example:** Given collected sizes `[12, 14, 16, 18, 24, 32, 48]`, the size closest to 16px is 16px (base index = 2):
322
+
323
+ | Size | Index | Relative | Token |
324
+ |------|-------|----------|-------|
325
+ | 12px | 0 | base - 2 | `--text-xs` |
326
+ | 14px | 1 | base - 1 | `--text-sm` |
327
+ | 16px | 2 | base | `--text-base` |
328
+ | 18px | 3 | base + 1 | `--text-lg` |
329
+ | 24px | 4 | base + 2 | `--text-xl` |
330
+ | 32px | 5 | base + 3 | `--text-2xl` |
331
+ | 48px | 6 | base + 4 | `--text-3xl` |
332
+
333
+ #### Font Weight Token Naming
334
+
335
+ Standard CSS weights map to named tokens:
336
+
337
+ | Weight | Token Name |
338
+ |--------|------------|
339
+ | 100 | `--font-thin` |
340
+ | 200 | `--font-extralight` |
341
+ | 300 | `--font-light` |
342
+ | 400 | `--font-regular` |
343
+ | 500 | `--font-medium` |
344
+ | 600 | `--font-semibold` |
345
+ | 700 | `--font-bold` |
346
+ | 800 | `--font-extrabold` |
347
+ | 900 | `--font-black` |
348
+
349
+ Non-standard weights (e.g., 450) use `--font-weight-{n}`.
350
+
351
+ > For how typography tokens are consumed during CSS generation (token lookup), see `design-to-code-typography.md` Section 11.
352
+
353
+ ---
354
+
355
+ ### 6. Effect Token Extraction
356
+
357
+ Effects are split into two categories: shadows (from `DROP_SHADOW` and `INNER_SHADOW` effects) and radii (from `cornerRadius` properties).
358
+
359
+ #### Shadow Collection
360
+
361
+ Each unique shadow is identified by its full CSS box-shadow value string. Drop shadows produce `Xpx Ypx Rpx Spx color`, inner shadows produce `inset Xpx Ypx Rpx Spx color`. Blur and background blur effects are not collected as shadow tokens (they do not have box-shadow equivalents).
362
+
363
+ Shadows are sorted by blur radius (smallest first) for naming.
364
+
365
+ #### Shadow Naming
366
+
367
+ Shadows use a size scale based on their position in the sorted list:
368
+
369
+ | Count | Naming |
370
+ |-------|--------|
371
+ | 1 shadow | `--shadow-md` (single shadow defaults to medium) |
372
+ | 2-6 shadows | `--shadow-sm`, `--shadow-md`, `--shadow-lg`, `--shadow-xl`, `--shadow-2xl`, `--shadow-3xl` |
373
+ | 7+ shadows | `--shadow-1`, `--shadow-2`, ... (numeric fallback) |
374
+
375
+ The naming starts at index 1 of the scale array (`sm`), not index 0 (`none`), because zero shadows would not be collected.
376
+
377
+ #### Radius Collection
378
+
379
+ Corner radii are collected from node `cornerRadius` properties. Per-corner radii (when `cornerRadius` is an object with `topLeft`, `topRight`, `bottomRight`, `bottomLeft`) are normalized to the maximum value for classification purposes.
380
+
381
+ Radii are sorted by value ascending for naming.
382
+
383
+ #### Radius Naming
384
+
385
+ | Condition | Token Name |
386
+ |-----------|------------|
387
+ | Value >= 9999 | `--radius-full` |
388
+ | Value === 0 | `--radius-none` |
389
+ | Single radius | `--radius-md` (defaults to medium) |
390
+ | 2-6 radii | `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`, `--radius-2xl`, `--radius-3xl` |
391
+ | 7+ radii | `--radius-1`, `--radius-2`, ... (numeric fallback) |
392
+
393
+ > For how fills, strokes, and effects are extracted from Figma nodes, see `design-to-code-visual.md`.
394
+
395
+ ---
396
+
397
+ ### 7. Threshold-Based Promotion
398
+
399
+ Not every unique value in a design should become a CSS variable. Single-use values add token bloat without reuse benefit. The promotion system filters tokens by usage count.
400
+
401
+ #### Promotion Logic
402
+
403
+ ```
404
+ promoteTokens(tokens, threshold = 2):
405
+ colors → keep where usageCount >= threshold
406
+ spacing → keep where usageCount >= threshold
407
+ typography.families → keep where usageCount >= threshold
408
+ typography.sizes → keep where usageCount >= threshold
409
+ typography.weights → keep where usageCount >= threshold
410
+ effects.shadows → keep where usageCount >= threshold
411
+ effects.radii → keep where usageCount >= threshold
412
+ figmaVariables → always keep local variables (filter out external)
413
+ ```
414
+
415
+ #### Default Threshold: 2
416
+
417
+ The default threshold of 2 means a value must appear at least twice in the design to be promoted to a token. This prevents:
418
+
419
+ - A one-off custom color from becoming `--color-accent-7`
420
+ - A single padding value from becoming `--spacing-17`
421
+ - A unique shadow definition from becoming `--shadow-5`
422
+
423
+ #### Figma Variables Override Threshold
424
+
425
+ Figma Variables are always promoted regardless of usage count because they represent **explicit design decisions** by the designer. If a designer creates a variable named `spacing/md` and binds it to a gap property, that variable should always appear in the token output even if it is used only once.
426
+
427
+ Only **local** Figma Variables are included in the promoted output. External library variables are excluded from the token file because their definitions come from the library file, not the current file. External variables appear in component CSS as `var(--name, fallback)` references.
428
+
429
+ #### Configurable Threshold
430
+
431
+ The threshold is configurable via the `promotionThreshold` option:
432
+
433
+ ```
434
+ threshold = 1 → Every unique value becomes a token (comprehensive but verbose)
435
+ threshold = 2 → Default — values used 2+ times become tokens
436
+ threshold = 3 → Stricter — values used 3+ times (fewer tokens, more inline values)
437
+ threshold = 0 → Everything promoted (same as threshold = 1)
438
+ ```
439
+
440
+ #### Promotion Example
441
+
442
+ Given these collected colors:
443
+
444
+ | Color | Usage Count | Promoted (threshold=2)? |
445
+ |-------|------------|------------------------|
446
+ | `#1a73e8` | 12 | Yes |
447
+ | `#333333` | 8 | Yes |
448
+ | `#f5f5f5` | 15 | Yes |
449
+ | `#ea4335` | 2 | Yes |
450
+ | `#ff69b4` | 1 | No (stays inline) |
451
+ | `#abcdef` | 1 | No (stays inline) |
452
+
453
+ The unpromoted colors (`#ff69b4`, `#abcdef`) remain as raw hex values in their component CSS instead of becoming CSS variables.
454
+
455
+ ---
456
+
457
+ ### 8. Token Naming Conventions
458
+
459
+ #### Category Prefix System
460
+
461
+ All tokens follow a `--{category}-{name}` pattern:
462
+
463
+ | Category | Prefix | Example |
464
+ |----------|--------|---------|
465
+ | Color | `--color-` | `--color-primary`, `--color-neutral-800` |
466
+ | Spacing | `--spacing-` | `--spacing-4`, `--spacing-12` |
467
+ | Font size | `--text-` | `--text-base`, `--text-2xl` |
468
+ | Font family | `--font-` | `--font-primary`, `--font-mono` |
469
+ | Font weight | `--font-` | `--font-bold`, `--font-regular` |
470
+ | Shadow | `--shadow-` | `--shadow-sm`, `--shadow-lg` |
471
+ | Radius | `--radius-` | `--radius-md`, `--radius-full` |
472
+
473
+ Note: Font family and font weight share the `--font-` prefix. This is acceptable because the names are distinct (family: `primary`, `mono`, `secondary`; weight: `bold`, `medium`, `regular`).
474
+
475
+ #### Figma Variable Path Conversion
476
+
477
+ Figma Variables use `/` as a path separator (e.g., `color/link/default`). These are converted to CSS custom property names using this algorithm:
478
+
479
+ ```
480
+ 1. Prepend "--"
481
+ 2. Lowercase the entire string
482
+ 3. Replace "/" with "-"
483
+ 4. Replace spaces with "-"
484
+ 5. Remove characters that are invalid in CSS custom property names
485
+
486
+ "color/link/default" → --color-link-default
487
+ "spacing/lg" → --spacing-lg
488
+ "Color/Background/Primary" → --color-background-primary
489
+ ```
490
+
491
+ #### The `--token-*` Convention
492
+
493
+ Some projects use a `--token-` prefix for all design tokens:
494
+
495
+ ```
496
+ Prefixed: --token-color-primary, --token-spacing-md, --token-radius-lg
497
+ Default: --color-primary, --spacing-4, --radius-md
498
+ ```
499
+
500
+ The double prefix (`--token-color-*`) provides clear namespacing when tokens coexist with other CSS custom properties (e.g., component-specific variables, CSS framework variables).
501
+
502
+ #### Conflict Resolution
503
+
504
+ When auto-detected tokens and Figma Variable tokens overlap (e.g., both produce a `--color-primary` token), the Figma Variable takes priority. This is enforced during the token lookup phase: Figma Variable bindings are checked first, then the auto-detected token lookup is consulted.
505
+
506
+ > For the lookup priority chain, see Section 9.
507
+
508
+ ---
509
+
510
+ ### 9. Token Lookup System
511
+
512
+ The token lookup system enables CSS variable substitution during code generation. It creates reverse maps from raw values to CSS variable references, so that `#1a73e8` in a fill can be replaced with `var(--color-primary)`.
513
+
514
+ #### TokenLookup Interface
515
+
516
+ ```
517
+ TokenLookup
518
+ ├── colors: Map<string, string> // normalized color → var(--color-name)
519
+ ├── spacing: Map<number, string> // pixel value → var(--spacing-name)
520
+ ├── fontFamilies: Map<string, string> // normalized family → var(--font-name)
521
+ ├── fontSizes: Map<number, string> // pixel value → var(--text-name)
522
+ ├── fontWeights: Map<number, string> // weight number → var(--font-name)
523
+ └── radii: Map<number, string> // pixel value → var(--radius-name)
524
+ ```
525
+
526
+ #### Building the Lookup
527
+
528
+ `buildTokenLookup(tokens)` creates the reverse map from promoted tokens:
529
+
530
+ ```
531
+ For each promoted color:
532
+ lookup.colors.set(normalizeColor(color.value), `var(${color.name})`)
533
+ // e.g., "#1a73e8" → "var(--color-primary)"
534
+
535
+ For each promoted spacing:
536
+ lookup.spacing.set(spacing.value, `var(${spacing.name})`)
537
+ // e.g., 16 → "var(--spacing-4)"
538
+
539
+ For each promoted font family:
540
+ lookup.fontFamilies.set(normalize(family.value), `var(${family.name})`)
541
+ // e.g., "inter" → "var(--font-primary)"
542
+
543
+ For each promoted font size:
544
+ lookup.fontSizes.set(size.value, `var(${size.name})`)
545
+ // e.g., 18 → "var(--text-lg)"
546
+
547
+ For each promoted font weight:
548
+ lookup.fontWeights.set(weight.value, `var(${weight.name})`)
549
+ // e.g., 700 → "var(--font-bold)"
550
+
551
+ For each promoted radius:
552
+ lookup.radii.set(radius.value, `var(${radius.name})`)
553
+ // e.g., 8 → "var(--radius-md)"
554
+ ```
555
+
556
+ #### Type-Specific Lookup Functions
557
+
558
+ Each token type has a dedicated lookup function that returns the `var()` reference if found, or the raw value as fallback:
559
+
560
+ | Function | Input | Output (if found) | Output (if not found) |
561
+ |----------|-------|-------------------|----------------------|
562
+ | `lookupColor(lookup, "#1a73e8")` | hex/rgba string | `var(--color-primary)` | `#1a73e8` |
563
+ | `lookupSpacing(lookup, 16)` | pixel number | `var(--spacing-4)` | `16px` |
564
+ | `lookupFontFamily(lookup, "Inter")` | family name | `var(--font-primary)` | `'Inter', sans-serif` |
565
+ | `lookupFontSize(lookup, 18)` | pixel number | `var(--text-lg)` | `18px` |
566
+ | `lookupFontWeight(lookup, 700)` | weight number | `var(--font-bold)` | `700` |
567
+ | `lookupRadius(lookup, 8)` | pixel number | `var(--radius-md)` | `8px` |
568
+
569
+ #### Figma Variable Priority
570
+
571
+ When a fill has a Figma Variable binding, the variable name is used directly instead of consulting the auto-detected token lookup. This is the `lookupFillColor` function's priority chain:
572
+
573
+ ```
574
+ lookupFillColor(fill, lookup):
575
+ 1. If fill.variable exists and has a name:
576
+ a. If variable is LOCAL → var(--css-name) (no fallback)
577
+ b. If variable is REMOTE → var(--css-name, rawColor) (with fallback)
578
+ 2. If no variable binding:
579
+ → lookupColor(lookup, fill.color) (auto-detected token)
580
+ 3. If no token match:
581
+ → raw color value (e.g., "#1a73e8")
582
+ ```
583
+
584
+ This ensures that explicit designer intent (Figma Variables) always takes precedence over heuristic-based token detection.
585
+
586
+ #### Integration with Generation Pipeline
587
+
588
+ The token lookup is built once before generation starts and passed into the generation functions. During CSS property generation, visual and typography modules check each raw value against the lookup:
589
+
590
+ ```
591
+ Generation sequence:
592
+ 1. collectAllTokens(node) → DesignTokens
593
+ 2. promoteTokens(tokens, 2) → DesignTokens (filtered)
594
+ 3. buildTokenLookup(promoted) → TokenLookup
595
+ 4. generateElement(node, ..., { tokenLookup })
596
+ ├── generateVisualStyles(fills, ..., lookup)
597
+ │ → background-color: var(--color-primary) ← from lookupColor
598
+ ├── generateTypographyStyles(text, lookup)
599
+ │ → font-family: var(--font-primary) ← from lookupFontFamily
600
+ │ → font-size: var(--text-lg) ← from lookupFontSize
601
+ └── generateLayoutStyles(layout, lookup)
602
+ → gap: var(--spacing-4) ← from lookupSpacing
603
+ ```
604
+
605
+ ---
606
+
607
+ ### 10. CSS Rendering
608
+
609
+ Token rendering generates a CSS file with a `:root` block containing all promoted tokens as CSS Custom Properties.
610
+
611
+ #### `:root` Block Structure
612
+
613
+ Tokens are organized by category with comment headers:
614
+
615
+ ```css
616
+ :root {
617
+ /* Figma Variables */
618
+ --color-link-default: #6366F1;
619
+ --spacing-lg: 24px;
620
+
621
+ /* Colors */
622
+ --color-neutral-100: #f5f5f5;
623
+ --color-neutral-800: #333333;
624
+ --color-primary: #1a73e8;
625
+ --color-success: #34a853;
626
+
627
+ /* Spacing */
628
+ --spacing-2: 8px;
629
+ --spacing-4: 16px;
630
+ --spacing-6: 24px;
631
+
632
+ /* Font Families */
633
+ --font-mono: 'Roboto Mono', monospace;
634
+ --font-primary: 'Inter', sans-serif;
635
+
636
+ /* Font Sizes */
637
+ --text-sm: 14px;
638
+ --text-base: 16px;
639
+ --text-lg: 18px;
640
+
641
+ /* Font Weights */
642
+ --font-regular: 400;
643
+ --font-bold: 700;
644
+
645
+ /* Shadows */
646
+ --shadow-md: 0px 4px 8px 0px rgba(0, 0, 0, 0.25);
647
+
648
+ /* Border Radii */
649
+ --radius-sm: 4px;
650
+ --radius-md: 8px;
651
+ --radius-lg: 12px;
652
+ }
653
+ ```
654
+
655
+ #### Category Ordering
656
+
657
+ Within the `:root` block, categories are rendered in this order:
658
+
659
+ 1. Figma Variables (without collection) -- simple variable bindings
660
+ 2. Figma Variables (with collections) -- grouped by collection name, default mode values
661
+ 3. Colors -- sorted alphabetically by name
662
+ 4. Spacing -- sorted by value ascending
663
+ 5. Font Families -- sorted alphabetically by name, with fallback stacks
664
+ 6. Font Sizes -- sorted by value ascending
665
+ 7. Font Weights -- sorted by value ascending
666
+ 8. Shadows -- sorted alphabetically by name
667
+ 9. Border Radii -- sorted by value ascending
668
+
669
+ #### Font Family Fallback Stacks
670
+
671
+ Font family tokens include appropriate fallback stacks:
672
+
673
+ ```css
674
+ --font-primary: 'Inter', sans-serif;
675
+ --font-mono: 'Roboto Mono', monospace;
676
+ ```
677
+
678
+ Monospace detection uses the same patterns as the font family naming (see Section 5).
679
+
680
+ #### Value Formatting
681
+
682
+ | Type | Format | Example |
683
+ |------|--------|---------|
684
+ | Color (hex) | Lowercase hex | `#1a73e8` |
685
+ | Color (rgba) | `rgba(R, G, B, A)` | `rgba(0, 0, 0, 0.25)` |
686
+ | Spacing | `{value}px` (rounded to 2 decimals) | `16px`, `8.5px` |
687
+ | Font size | `{value}px` | `18px` |
688
+ | Font weight | Raw number (no unit) | `700` |
689
+ | Shadow | Full CSS box-shadow value | `0px 4px 8px 0px rgba(0, 0, 0, 0.25)` |
690
+ | Radius | `{value}px` (rounded to 2 decimals) | `8px`, `12px` |
691
+
692
+ #### Mode-Aware Rendering: Breakpoint Media Queries
693
+
694
+ When variable collections have breakpoint mode types, non-default modes are rendered as `@media (min-width)` overrides:
695
+
696
+ ```css
697
+ :root {
698
+ /* Spacing (mobile = default) */
699
+ --spacing-lg: 16px;
700
+ }
701
+
702
+ @media (min-width: 768px) {
703
+ :root {
704
+ --spacing-lg: 24px;
705
+ }
706
+ }
707
+
708
+ @media (min-width: 1024px) {
709
+ :root {
710
+ --spacing-lg: 32px;
711
+ }
712
+ }
713
+ ```
714
+
715
+ Breakpoint modes are sorted from smallest to largest (mobile-first). The default mode (smallest breakpoint, typically "mobile") provides base `:root` values, and larger breakpoints override specific values.
716
+
717
+ #### Mode-Aware Rendering: Theme Selectors
718
+
719
+ When variable collections have theme mode types, non-default modes generate both a `prefers-color-scheme` media query and a `[data-theme]` attribute selector:
720
+
721
+ ```css
722
+ :root {
723
+ /* Theme - Light (default) */
724
+ --color-bg: #ffffff;
725
+ --color-text: #1a1a1a;
726
+ }
727
+
728
+ @media (prefers-color-scheme: dark) {
729
+ :root {
730
+ --color-bg: #1a1a1a;
731
+ --color-text: #ffffff;
732
+ }
733
+ }
734
+
735
+ [data-theme="dark"] {
736
+ --color-bg: #1a1a1a;
737
+ --color-text: #ffffff;
738
+ }
739
+ ```
740
+
741
+ The dual output allows both automatic theme detection (via OS preference) and manual theme switching (via JavaScript setting `data-theme` on the root element).
742
+
743
+ > For Figma Variables mode detection and breakpoint/theme classification, see `figma-api-variables.md`.
744
+
745
+ ---
746
+
747
+ ### 11. SCSS Rendering
748
+
749
+ For SCSS consumers, tokens are rendered as `$variable` declarations instead of CSS custom properties.
750
+
751
+ #### Conversion Rule
752
+
753
+ The conversion is straightforward:
754
+
755
+ ```
756
+ CSS: --color-primary: #1a73e8;
757
+ SCSS: $color-primary: #1a73e8;
758
+
759
+ CSS: var(--color-primary)
760
+ SCSS: $color-primary
761
+ ```
762
+
763
+ The `--` prefix becomes `$`, and `var()` wrappers are removed.
764
+
765
+ #### SCSS File Structure
766
+
767
+ ```scss
768
+ // _variables.scss
769
+
770
+ // Colors
771
+ $color-primary: #1a73e8;
772
+ $color-neutral-100: #f5f5f5;
773
+ $color-neutral-800: #333333;
774
+
775
+ // Spacing
776
+ $spacing-2: 8px;
777
+ $spacing-4: 16px;
778
+ $spacing-6: 24px;
779
+
780
+ // Font Families
781
+ $font-primary: 'Inter', sans-serif;
782
+ $font-mono: 'Roboto Mono', monospace;
783
+
784
+ // Font Sizes
785
+ $text-sm: 14px;
786
+ $text-base: 16px;
787
+ $text-lg: 18px;
788
+
789
+ // Font Weights
790
+ $font-regular: 400;
791
+ $font-bold: 700;
792
+
793
+ // Shadows
794
+ $shadow-md: 0px 4px 8px 0px rgba(0, 0, 0, 0.25);
795
+
796
+ // Border Radii
797
+ $radius-sm: 4px;
798
+ $radius-md: 8px;
799
+ $radius-lg: 12px;
800
+ ```
801
+
802
+ #### SCSS Component Styles
803
+
804
+ In component SCSS files, `var(--name)` references are converted to `$name` references:
805
+
806
+ ```scss
807
+ // styles.scss
808
+ @import 'variables';
809
+ @import 'reset';
810
+ @import 'mixins';
811
+
812
+ .card {
813
+ background-color: $color-neutral-100;
814
+ border-radius: $radius-lg;
815
+ box-shadow: $shadow-md;
816
+ }
817
+
818
+ .card__title {
819
+ color: $color-neutral-800;
820
+ font-size: $text-2xl;
821
+ font-weight: $font-bold;
822
+ }
823
+ ```
824
+
825
+ #### Limitations of SCSS Rendering
826
+
827
+ SCSS variables are compile-time constants, not runtime-dynamic like CSS Custom Properties. This means:
828
+
829
+ - **No theme switching at runtime** -- SCSS variables are baked in at build time
830
+ - **No breakpoint mode overrides** -- SCSS cannot change variable values in media queries
831
+ - **No `var()` fallbacks** -- SCSS variable references fail at compile time if undefined
832
+
833
+ SCSS rendering is provided for projects that already use SCSS tooling. For projects that need runtime theming, responsive tokens, or Figma Variable modes, CSS Custom Properties are the recommended output.
834
+
835
+ > For the complete SCSS export file structure, see `css-strategy.md` Section 9.
836
+
837
+ ---
838
+
839
+ ### 12. Tailwind Config Generation
840
+
841
+ Design tokens can be mapped to a Tailwind theme configuration, enabling Tailwind utility classes to reference the same token values used in CSS Modules.
842
+
843
+ #### Token Category to Tailwind Theme Mapping
844
+
845
+ | Token Category | Tailwind Theme Key | Tailwind Class Example |
846
+ |---------------|-------------------|----------------------|
847
+ | Colors | `theme.colors` | `bg-primary`, `text-neutral-800` |
848
+ | Spacing | `theme.spacing` | `gap-4`, `p-6` |
849
+ | Font sizes | `theme.fontSize` | `text-lg`, `text-2xl` |
850
+ | Font families | `theme.fontFamily` | `font-primary`, `font-mono` |
851
+ | Border radii | `theme.borderRadius` | `rounded-md`, `rounded-lg` |
852
+ | Shadows | `theme.boxShadow` | `shadow-md`, `shadow-lg` |
853
+
854
+ #### CSS Variable References in Tailwind Config
855
+
856
+ The Tailwind config references CSS Custom Properties, not raw values. This creates a bridge between Layer 1 (Tailwind classes) and Layer 2 (CSS Custom Properties):
857
+
858
+ ```js
859
+ // tailwind.config.js (generated from tokens)
860
+ export default {
861
+ theme: {
862
+ extend: {
863
+ colors: {
864
+ primary: 'var(--color-primary)',
865
+ secondary: 'var(--color-secondary)',
866
+ success: 'var(--color-success)',
867
+ warning: 'var(--color-warning)',
868
+ error: 'var(--color-error)',
869
+ neutral: {
870
+ 100: 'var(--color-neutral-100)',
871
+ 300: 'var(--color-neutral-300)',
872
+ 500: 'var(--color-neutral-500)',
873
+ 800: 'var(--color-neutral-800)',
874
+ 900: 'var(--color-neutral-900)',
875
+ },
876
+ },
877
+ spacing: {
878
+ 1: 'var(--spacing-1)', // 4px
879
+ 2: 'var(--spacing-2)', // 8px
880
+ 3: 'var(--spacing-3)', // 12px
881
+ 4: 'var(--spacing-4)', // 16px
882
+ 6: 'var(--spacing-6)', // 24px
883
+ 8: 'var(--spacing-8)', // 32px
884
+ },
885
+ fontSize: {
886
+ xs: 'var(--text-xs)',
887
+ sm: 'var(--text-sm)',
888
+ base: 'var(--text-base)',
889
+ lg: 'var(--text-lg)',
890
+ xl: 'var(--text-xl)',
891
+ '2xl': 'var(--text-2xl)',
892
+ '3xl': 'var(--text-3xl)',
893
+ },
894
+ fontFamily: {
895
+ primary: 'var(--font-primary)',
896
+ mono: 'var(--font-mono)',
897
+ },
898
+ borderRadius: {
899
+ sm: 'var(--radius-sm)',
900
+ md: 'var(--radius-md)',
901
+ lg: 'var(--radius-lg)',
902
+ full: 'var(--radius-full)',
903
+ },
904
+ boxShadow: {
905
+ sm: 'var(--shadow-sm)',
906
+ md: 'var(--shadow-md)',
907
+ lg: 'var(--shadow-lg)',
908
+ },
909
+ },
910
+ },
911
+ }
912
+ ```
913
+
914
+ #### Benefits of CSS Variable References
915
+
916
+ Using `var()` references instead of raw values means:
917
+
918
+ 1. **Theme switching works** -- Changing `:root` token values changes all Tailwind utility outputs
919
+ 2. **Responsive tokens work** -- Breakpoint mode overrides in `:root` propagate through Tailwind
920
+ 3. **Single source of truth** -- Tokens are defined once in `tokens.css`, consumed by both Tailwind classes and CSS Module rules
921
+
922
+ #### Tailwind v4 Automatic Detection
923
+
924
+ Tailwind v4 can automatically detect CSS custom properties and make them available as utility classes without explicit theme configuration. When a `tokens.css` file defines `--color-primary`, Tailwind v4 can generate a `bg-[--color-primary]` utility automatically.
925
+
926
+ However, explicit theme configuration is still recommended for cleaner class names (`bg-primary` vs `bg-[--color-primary]`) and for IDE autocompletion support.
927
+
928
+ #### Color Token Flattening
929
+
930
+ The neutral color scale uses nested objects in the Tailwind config:
931
+
932
+ ```js
933
+ // Nested:
934
+ neutral: {
935
+ 100: 'var(--color-neutral-100)',
936
+ 800: 'var(--color-neutral-800)',
937
+ }
938
+ // Produces: bg-neutral-100, text-neutral-800
939
+
940
+ // Flat (alternative):
941
+ 'neutral-100': 'var(--color-neutral-100)',
942
+ 'neutral-800': 'var(--color-neutral-800)',
943
+ // Produces: bg-neutral-100, text-neutral-800 (same output)
944
+ ```
945
+
946
+ Both approaches produce the same Tailwind classes. The nested approach is preferred for readability.
947
+
948
+ ---
949
+
950
+ ## Cross-References
951
+
952
+ - **`css-strategy.md`** -- How tokens integrate with the three-layer CSS architecture. Token placement at Layer 2, consumption patterns in Layer 3, Tailwind theme bridge to Layer 1.
953
+ - **`design-tokens-variables.md`** -- Figma Variables deep dive: mode detection and classification, variable resolution chains, bound variable extraction, mode-aware CSS rendering (breakpoint media queries, theme selectors), fallback strategies, scope-to-CSS-property mapping.
954
+ - **`figma-api-variables.md`** -- Variables API endpoints, data model, resolution mechanics. Provides the raw data for Figma Variable token extraction. Token extraction priority order.
955
+ - **`design-to-code-layout.md`** -- Layout property extraction that produces spacing values collected by the spacing token extractor. Variable bindings on gap and padding properties.
956
+ - **`design-to-code-visual.md`** -- Visual property extraction that produces colors, radii, and shadows collected by the color and effect token extractors. HSL-based semantic color naming. Token lookup integration during CSS generation.
957
+ - **`design-to-code-typography.md`** -- Typography extraction that produces font families, sizes, and weights collected by the typography token extractor. Type scale naming. Token lookup during font property generation.
958
+ - **`design-to-code-assets.md`** -- Asset management is independent of tokens (assets are files, not CSS values). Asset nodes skip visual style generation and therefore do not contribute to token collection.
959
+ - **`design-to-code-semantic.md`** -- Semantic HTML generation that creates the element structure consuming token-substituted CSS. BEM class names used as selectors in token-consuming CSS Module rules.
960
+ - **`payload-figma-mapping.md`** -- Figma-to-PayloadCMS mapping pipeline that includes a design token bridge step (Section 5). Extracted Figma tokens are mapped to `--token-*` CSS custom properties consumed by block CSS Modules, completing the Figma-to-CMS token chain.