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.
- package/README.md +133 -0
- package/bin/install.js +328 -0
- package/knowledge/README.md +62 -0
- package/knowledge/css-strategy.md +973 -0
- package/knowledge/design-to-code-assets.md +855 -0
- package/knowledge/design-to-code-layout.md +929 -0
- package/knowledge/design-to-code-semantic.md +1085 -0
- package/knowledge/design-to-code-typography.md +1003 -0
- package/knowledge/design-to-code-visual.md +1145 -0
- package/knowledge/design-tokens-variables.md +1261 -0
- package/knowledge/design-tokens.md +960 -0
- package/knowledge/figma-api-devmode.md +894 -0
- package/knowledge/figma-api-plugin.md +920 -0
- package/knowledge/figma-api-rest.md +742 -0
- package/knowledge/figma-api-variables.md +848 -0
- package/knowledge/figma-api-webhooks.md +876 -0
- package/knowledge/payload-blocks.md +1184 -0
- package/knowledge/payload-figma-mapping.md +1210 -0
- package/knowledge/payload-visual-builder.md +1004 -0
- package/knowledge/plugin-architecture.md +1176 -0
- package/knowledge/plugin-best-practices.md +1206 -0
- package/knowledge/plugin-codegen.md +1313 -0
- package/package.json +31 -0
- package/skills/README.md +103 -0
- package/skills/audit-plugin/SKILL.md +244 -0
- package/skills/build-codegen-plugin/SKILL.md +279 -0
- package/skills/build-importer/SKILL.md +320 -0
- package/skills/build-plugin/SKILL.md +199 -0
- package/skills/build-token-pipeline/SKILL.md +363 -0
- package/skills/ref-html/SKILL.md +290 -0
- package/skills/ref-layout/SKILL.md +150 -0
- package/skills/ref-payload-block/SKILL.md +415 -0
- package/skills/ref-react/SKILL.md +222 -0
- 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.
|