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,929 @@
1
+ # Auto Layout → CSS Flexbox Mapping
2
+
3
+ ## Purpose
4
+
5
+ Authoritative reference for converting Figma Auto Layout properties to CSS Flexbox. Encodes production-proven mapping rules covering the complete pipeline: extracting Auto Layout data from Figma nodes, transforming it into intermediate types, and generating pixel-accurate CSS. This is the foundational design-to-code module that all component generation depends on.
6
+
7
+ ## When to Use
8
+
9
+ Reference this module when you need to:
10
+
11
+ - Convert a Figma Auto Layout frame into CSS Flexbox (`display: flex`)
12
+ - Determine the correct CSS for Figma sizing modes (FIXED, HUG, FILL)
13
+ - Map Figma alignment properties to `justify-content` and `align-items`
14
+ - Generate gap, padding, and spacing CSS from Figma properties
15
+ - Handle flex-wrap and `align-content` for wrapped layouts
16
+ - Apply min/max constraints to flex containers and children
17
+ - Position absolute children within Auto Layout parents
18
+ - Convert non-Auto-Layout frames (GROUP, legacy FRAME) to CSS
19
+ - Build responsive CSS from multiple Figma frames representing breakpoints
20
+ - Avoid common design-to-code pitfalls (flex-basis, STRETCH, sizing interactions)
21
+
22
+ ---
23
+
24
+ ## Content
25
+
26
+ ### 1. Auto Layout → Flexbox Core Mapping
27
+
28
+ Figma's Auto Layout maps directly to CSS Flexbox. Any FRAME, COMPONENT, or INSTANCE node with `layoutMode` set to `HORIZONTAL` or `VERTICAL` is an Auto Layout container.
29
+
30
+ #### Container Detection
31
+
32
+ Only these node types can have Auto Layout:
33
+ - `FRAME`
34
+ - `COMPONENT`
35
+ - `INSTANCE`
36
+
37
+ When `layoutMode` is `NONE`, the frame has no Auto Layout — children use absolute or constraint-based positioning (see Section 7).
38
+
39
+ #### Flex Direction
40
+
41
+ | Figma `layoutMode` | CSS `flex-direction` | Intermediate Mode |
42
+ |---------------------|----------------------|-------------------|
43
+ | `HORIZONTAL` | `row` | `FLEX_ROW` |
44
+ | `VERTICAL` | `column` | `FLEX_COL` |
45
+ | `NONE` | _(no flexbox)_ | `NONE` |
46
+
47
+ Every Auto Layout container generates:
48
+
49
+ ```css
50
+ display: flex;
51
+ flex-direction: row; /* or column */
52
+ ```
53
+
54
+ #### Primary Axis Alignment (justify-content)
55
+
56
+ `primaryAxisAlignItems` controls distribution along the flex direction.
57
+
58
+ | Figma `primaryAxisAlignItems` | CSS `justify-content` |
59
+ |-------------------------------|-----------------------|
60
+ | `MIN` | `flex-start` |
61
+ | `CENTER` | `center` |
62
+ | `MAX` | `flex-end` |
63
+ | `SPACE_BETWEEN` | `space-between` |
64
+
65
+ #### Counter Axis Alignment (align-items)
66
+
67
+ `counterAxisAlignItems` controls alignment perpendicular to the flex direction.
68
+
69
+ | Figma `counterAxisAlignItems` | CSS `align-items` |
70
+ |-------------------------------|-------------------|
71
+ | `MIN` | `flex-start` |
72
+ | `CENTER` | `center` |
73
+ | `MAX` | `flex-end` |
74
+ | `BASELINE` | `baseline` |
75
+
76
+ > **Important:** `STRETCH` does not appear in the `counterAxisAlignItems` extraction mapping. Stretch behavior is controlled at the child level through sizing modes (FILL on the counter-axis). See Section 2 for how FILL on the counter-axis produces `align-self: stretch`.
77
+
78
+ #### Complete Container Example
79
+
80
+ A horizontal Auto Layout frame with center alignment and space-between distribution:
81
+
82
+ ```
83
+ Figma properties:
84
+ layoutMode: HORIZONTAL
85
+ primaryAxisAlignItems: SPACE_BETWEEN
86
+ counterAxisAlignItems: CENTER
87
+ ```
88
+
89
+ ```css
90
+ display: flex;
91
+ flex-direction: row;
92
+ justify-content: space-between;
93
+ align-items: center;
94
+ ```
95
+
96
+ ---
97
+
98
+ ### 2. Sizing Modes (CRITICAL)
99
+
100
+ Sizing modes are the **most common source of bugs** in Figma-to-CSS generation. Every child in an Auto Layout container has two sizing properties:
101
+
102
+ - `layoutSizingHorizontal`: `FIXED` | `HUG` | `FILL`
103
+ - `layoutSizingVertical`: `FIXED` | `HUG` | `FILL`
104
+
105
+ These interact with the parent's flex direction. The same property (e.g., `layoutSizingHorizontal`) produces different CSS depending on whether horizontal is the **primary axis** (parent is `FLEX_ROW`) or the **counter axis** (parent is `FLEX_COL`).
106
+
107
+ #### Intermediate Types
108
+
109
+ ```typescript
110
+ interface LayoutChildProperties {
111
+ flexGrow: number; // From layoutGrow (0 or 1)
112
+ alignSelf: 'auto' | 'flex-start' | 'center' | 'flex-end' | 'stretch';
113
+ sizingHorizontal: 'FIXED' | 'HUG' | 'FILL';
114
+ sizingVertical: 'FIXED' | 'HUG' | 'FILL';
115
+ minWidth?: number;
116
+ maxWidth?: number;
117
+ minHeight?: number;
118
+ maxHeight?: number;
119
+ }
120
+ ```
121
+
122
+ #### Sizing Mode → CSS Decision Tree
123
+
124
+ ```
125
+ For each child in an Auto Layout parent:
126
+
127
+ 1. Determine which axis is PRIMARY (same as parent flex-direction)
128
+ - Parent FLEX_ROW → horizontal = primary, vertical = counter
129
+ - Parent FLEX_COL → vertical = primary, horizontal = counter
130
+
131
+ 2. PRIMARY AXIS sizing:
132
+ ├─ FILL → flex-grow: 1; flex-basis: 0;
133
+ ├─ FIXED → flex-shrink: 0; (explicit width/height set separately from bounds)
134
+ └─ HUG → (omit — let content determine size)
135
+
136
+ 3. COUNTER AXIS sizing:
137
+ ├─ FILL → align-self: stretch;
138
+ │ (EXCEPTION: if child has max-width/max-height constraint,
139
+ │ use width: 100% / height: 100% instead — see Section 2.5)
140
+ ├─ FIXED → (explicit width/height set separately from bounds)
141
+ └─ HUG → (omit — let content determine size)
142
+ ```
143
+
144
+ #### 2.1 FILL on Primary Axis
145
+
146
+ FILL on the primary axis means the child expands to take available space. **Both properties are required:**
147
+
148
+ ```css
149
+ flex-grow: 1;
150
+ flex-basis: 0;
151
+ ```
152
+
153
+ > **CRITICAL: `flex-basis: 0` is essential.** Without it, `flex-basis` defaults to `auto`, which uses the element's content size as the starting point. This causes content-heavy siblings to take disproportionately more space while empty elements (like image placeholders) get squeezed to near-zero width. Setting `flex-basis: 0` ensures all FILL children start from zero and share space equally based on their `flex-grow` factor.
154
+
155
+ **Example — two FILL children in a row:**
156
+
157
+ ```
158
+ Parent: layoutMode: HORIZONTAL
159
+ Child A: layoutSizingHorizontal: FILL (lots of text content)
160
+ Child B: layoutSizingHorizontal: FILL (empty image placeholder)
161
+ ```
162
+
163
+ Correct CSS:
164
+ ```css
165
+ /* Both children get equal space */
166
+ .child-a { flex-grow: 1; flex-basis: 0; }
167
+ .child-b { flex-grow: 1; flex-basis: 0; }
168
+ ```
169
+
170
+ Wrong CSS (without flex-basis: 0):
171
+ ```css
172
+ /* Child A takes 80%+ because its content is larger */
173
+ .child-a { flex-grow: 1; } /* BAD — flex-basis defaults to auto */
174
+ .child-b { flex-grow: 1; } /* BAD — gets squeezed */
175
+ ```
176
+
177
+ #### 2.2 FIXED on Primary Axis
178
+
179
+ FIXED means the child maintains an exact size. On the primary axis, prevent flex shrinking:
180
+
181
+ ```css
182
+ flex-shrink: 0;
183
+ width: 200px; /* or height, depending on axis */
184
+ ```
185
+
186
+ The explicit dimension comes from the node's bounds (set elsewhere in generation). The `flex-shrink: 0` prevents the flex container from compressing this element below its specified size.
187
+
188
+ #### 2.3 HUG (Content-Based)
189
+
190
+ HUG means the element sizes to its content. No explicit sizing CSS is needed — the default flex behavior handles it:
191
+
192
+ ```css
193
+ /* No width/height, no flex-grow, no flex-shrink override */
194
+ /* The element naturally sizes to its content */
195
+ ```
196
+
197
+ #### 2.4 FILL on Counter Axis
198
+
199
+ FILL on the counter axis means the child stretches to fill the parent's cross-axis dimension:
200
+
201
+ ```css
202
+ align-self: stretch;
203
+ ```
204
+
205
+ **Example — column layout with horizontal FILL child:**
206
+
207
+ ```
208
+ Parent: layoutMode: VERTICAL
209
+ Child: layoutSizingHorizontal: FILL
210
+ ```
211
+
212
+ ```css
213
+ .child {
214
+ align-self: stretch;
215
+ }
216
+ ```
217
+
218
+ #### 2.5 FILL on Counter Axis with Max Constraint (Special Case)
219
+
220
+ When a child has FILL on the counter-axis **and** a max-width or max-height constraint, using `align-self: stretch` would ignore the constraint in some cases. Instead, use percentage sizing:
221
+
222
+ ```
223
+ Parent: layoutMode: HORIZONTAL (primary = horizontal)
224
+ Child: layoutSizingVertical: FILL, maxHeight: 300
225
+ ```
226
+
227
+ ```css
228
+ .child {
229
+ height: 100%;
230
+ max-height: 300px;
231
+ }
232
+ ```
233
+
234
+ This allows the parent's `align-items` to position the element correctly when the max constraint caps its size (e.g., centering a capped-height element).
235
+
236
+ The rule:
237
+ - **FILL counter-axis + max constraint on counter dimension** → `width: 100%` / `height: 100%` instead of `align-self: stretch`
238
+ - **FILL counter-axis without max constraint** → `align-self: stretch`
239
+
240
+ #### 2.6 Mixed Sizing
241
+
242
+ Children within the same parent can have different sizing modes. This is valid and common:
243
+
244
+ ```
245
+ Parent: layoutMode: HORIZONTAL
246
+
247
+ Child 1: sizingHorizontal: FIXED (sidebar, 280px)
248
+ Child 2: sizingHorizontal: FILL (main content, takes remaining)
249
+ Child 3: sizingHorizontal: HUG (icon, sizes to content)
250
+ ```
251
+
252
+ ```css
253
+ .sidebar { width: 280px; flex-shrink: 0; }
254
+ .main { flex-grow: 1; flex-basis: 0; }
255
+ .icon { /* no explicit sizing */ }
256
+ ```
257
+
258
+ #### 2.7 Align Self Override
259
+
260
+ A child's `layoutAlign` property in Figma can override the parent's `counterAxisAlignItems`. This maps to `align-self`:
261
+
262
+ | Figma `layoutAlign` | CSS `align-self` |
263
+ |----------------------|------------------|
264
+ | `INHERIT` | `auto` |
265
+ | `MIN` | `flex-start` |
266
+ | `CENTER` | `center` |
267
+ | `MAX` | `flex-end` |
268
+ | `STRETCH` | `stretch` |
269
+
270
+ > **Note:** `align-self` is only emitted when it differs from `auto` **and** the child is not already using `width: 100%` / `height: 100%` for the FILL + max-constraint pattern. The max-constraint pattern takes precedence.
271
+
272
+ ---
273
+
274
+ ### 3. Gap & Spacing
275
+
276
+ #### Primary Axis Gap
277
+
278
+ `itemSpacing` sets the gap between children along the primary axis:
279
+
280
+ | Figma Property | CSS Property |
281
+ |-----------------|--------------|
282
+ | `itemSpacing` | `gap` |
283
+
284
+ ```
285
+ Figma: itemSpacing: 16
286
+ CSS: gap: 16px;
287
+ ```
288
+
289
+ > **Important:** `itemSpacing` is spacing **between** children, not padding around them. This maps to CSS `gap`, not `padding`.
290
+
291
+ #### Counter Axis Gap (Wrap Mode Only)
292
+
293
+ `counterAxisSpacing` sets the gap between wrapped rows/columns. It only applies when `layoutWrap: WRAP`:
294
+
295
+ | Figma Property | CSS Property |
296
+ |-----------------------|----------------|
297
+ | `counterAxisSpacing` | `row-gap` or `column-gap` (depending on direction) |
298
+
299
+ #### Gap Direction Mapping
300
+
301
+ The CSS gap properties depend on the flex direction:
302
+
303
+ | Layout Mode | Primary Gap (`itemSpacing`) | Cross Gap (`counterAxisSpacing`) |
304
+ |-------------|----------------------------|----------------------------------|
305
+ | `FLEX_ROW` | `column-gap` | `row-gap` |
306
+ | `FLEX_COL` | `row-gap` | `column-gap` |
307
+
308
+ **Shorthand optimization:** When both `row-gap` and `column-gap` are equal, use the `gap` shorthand:
309
+
310
+ ```css
311
+ /* Both gaps equal → shorthand */
312
+ gap: 16px;
313
+
314
+ /* Gaps differ → individual properties */
315
+ row-gap: 8px;
316
+ column-gap: 16px;
317
+ ```
318
+
319
+ #### Variable Bindings for Gap
320
+
321
+ When `itemSpacing` or `counterAxisSpacing` is bound to a Figma Variable, generate CSS `var()` references instead of raw pixel values:
322
+
323
+ ```typescript
324
+ interface VariableReference {
325
+ id: string; // Figma variable ID
326
+ name: string; // Variable name path (e.g., "spacing/md")
327
+ isLocal: boolean; // Local to this file vs. external library
328
+ }
329
+ ```
330
+
331
+ **CSS generation with variables:**
332
+
333
+ ```css
334
+ /* Local variable (defined in same file) — no fallback needed */
335
+ gap: var(--spacing-md);
336
+
337
+ /* External library variable — include px fallback */
338
+ gap: var(--spacing-md, 16px);
339
+ ```
340
+
341
+ The variable name is converted from Figma's slash-delimited path to CSS custom property naming: `spacing/md` → `--spacing-md`.
342
+
343
+ #### Padding
344
+
345
+ Figma padding is per-side: `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft`.
346
+
347
+ **Shorthand optimization:**
348
+
349
+ ```css
350
+ /* All four sides equal */
351
+ padding: 24px;
352
+
353
+ /* Mixed sides — full longhand */
354
+ padding: 24px 16px 24px 16px;
355
+ ```
356
+
357
+ **With variable bindings:**
358
+
359
+ ```css
360
+ /* All four sides bound to the same variable */
361
+ padding: var(--spacing-lg);
362
+
363
+ /* Mixed — some variable-bound, some raw */
364
+ padding: var(--spacing-lg, 24px) 16px var(--spacing-lg, 24px) 16px;
365
+ ```
366
+
367
+ The shorthand optimization checks both the numeric values **and** the variable IDs. All four sides must have the same value and the same variable binding (or all unbound) to use the single-value shorthand.
368
+
369
+ ---
370
+
371
+ ### 4. Wrap Mode
372
+
373
+ #### Enabling Wrap
374
+
375
+ | Figma Property | CSS Property |
376
+ |----------------|-----------------|
377
+ | `layoutWrap: WRAP` | `flex-wrap: wrap` |
378
+
379
+ When `layoutWrap` is not `WRAP` (default `NO_WRAP`), no `flex-wrap` property is emitted.
380
+
381
+ #### Align Content (Wrapped Line Distribution)
382
+
383
+ When wrapping is enabled, `counterAxisAlignContent` controls how wrapped lines are distributed:
384
+
385
+ | Figma `counterAxisAlignContent` | CSS `align-content` |
386
+ |---------------------------------|---------------------|
387
+ | `AUTO` | `flex-start` |
388
+ | `SPACE_BETWEEN` | `space-between` |
389
+
390
+ `align-content` is only emitted when `layoutWrap: WRAP`.
391
+
392
+ #### Counter Axis Gap in Wrap Mode
393
+
394
+ `counterAxisSpacing` becomes relevant in wrap mode — it controls the spacing between wrapped rows/columns. Without wrap, `counterAxisSpacing` has no visual effect.
395
+
396
+ #### Wrap Example
397
+
398
+ ```
399
+ Figma:
400
+ layoutMode: HORIZONTAL
401
+ layoutWrap: WRAP
402
+ itemSpacing: 16
403
+ counterAxisSpacing: 12
404
+ counterAxisAlignContent: SPACE_BETWEEN
405
+ ```
406
+
407
+ ```css
408
+ display: flex;
409
+ flex-direction: row;
410
+ flex-wrap: wrap;
411
+ column-gap: 16px;
412
+ row-gap: 12px;
413
+ align-content: space-between;
414
+ ```
415
+
416
+ ---
417
+
418
+ ### 5. Min/Max Constraints
419
+
420
+ Figma nodes can have min/max size constraints that map directly to CSS:
421
+
422
+ | Figma Property | CSS Property | Extraction Rule |
423
+ |----------------|--------------|-----------------|
424
+ | `minWidth` | `min-width` | Only if > 0 |
425
+ | `maxWidth` | `max-width` | Only if < Infinity |
426
+ | `minHeight` | `min-height` | Only if > 0 |
427
+ | `maxHeight` | `max-height` | Only if < Infinity |
428
+
429
+ Constraints apply to both containers and children. They are extracted when the value is meaningful (not zero for min, not infinity for max).
430
+
431
+ #### Container Constraints
432
+
433
+ ```typescript
434
+ // Extraction: only include non-trivial values
435
+ if (frame.minWidth !== null && frame.minWidth > 0) {
436
+ layout.minWidth = frame.minWidth;
437
+ }
438
+ if (frame.maxWidth !== null && frame.maxWidth < Infinity) {
439
+ layout.maxWidth = frame.maxWidth;
440
+ }
441
+ ```
442
+
443
+ ```css
444
+ /* Container with constraints */
445
+ .card {
446
+ display: flex;
447
+ flex-direction: column;
448
+ min-width: 200px;
449
+ max-width: 600px;
450
+ }
451
+ ```
452
+
453
+ #### Child Constraints
454
+
455
+ Children in Auto Layout also support min/max. Common patterns:
456
+
457
+ ```css
458
+ /* FILL child with max-width — prevents over-expansion */
459
+ .content {
460
+ flex-grow: 1;
461
+ flex-basis: 0;
462
+ max-width: 800px;
463
+ }
464
+
465
+ /* FILL child with min-width — prevents collapse */
466
+ .sidebar {
467
+ flex-grow: 1;
468
+ flex-basis: 0;
469
+ min-width: 200px;
470
+ }
471
+ ```
472
+
473
+ #### Interaction with Sizing Modes
474
+
475
+ - **FILL + max constraint**: The element grows to fill available space but caps at max. See Section 2.5 for the counter-axis special case.
476
+ - **FIXED + min constraint**: The element has a fixed size but won't go below min (relevant when `flex-shrink` allows some compression).
477
+ - **HUG + max constraint**: Content-sized but capped — useful for text containers that shouldn't exceed a reading width.
478
+
479
+ ---
480
+
481
+ ### 6. Absolute Position Children
482
+
483
+ Figma allows placing children with `layoutPositioning: ABSOLUTE` inside an Auto Layout parent. These children are taken out of the Auto Layout flow.
484
+
485
+ #### CSS Generation
486
+
487
+ ```css
488
+ /* Auto Layout parent automatically becomes positioning context */
489
+ .parent {
490
+ display: flex;
491
+ flex-direction: column;
492
+ position: relative; /* Required for absolute children */
493
+ }
494
+
495
+ /* Absolutely positioned child within Auto Layout */
496
+ .overlay {
497
+ position: absolute;
498
+ /* Offsets derived from constraint-based positioning */
499
+ top: 8px;
500
+ right: 8px;
501
+ }
502
+ ```
503
+
504
+ #### Key Rules
505
+
506
+ 1. **Parent needs `position: relative`**: In Figma, the Auto Layout frame is implicitly the positioning context. In CSS, you must add `position: relative` explicitly.
507
+ 2. **Child is removed from flex flow**: The absolute child does not participate in flex sizing or gap distribution.
508
+ 3. **Offset calculation**: The child's position within the parent is determined by its constraints (see Section 7 for constraint mapping), not by flex ordering.
509
+
510
+ ---
511
+
512
+ ### 7. Non-Auto-Layout Frames (GROUP, Legacy FRAME)
513
+
514
+ When a frame has `layoutMode: NONE` or the node is a `GROUP`, children are positioned using absolute coordinates and constraints.
515
+
516
+ #### GROUP Nodes
517
+
518
+ GROUP nodes have no layout mode. Their children use absolute positioning based on bounds:
519
+
520
+ ```css
521
+ /* GROUP container */
522
+ .group {
523
+ position: relative;
524
+ overflow: hidden; /* Matches Figma's default "Clip content" */
525
+ }
526
+
527
+ /* GROUP children — absolute positioned */
528
+ .group__child {
529
+ position: absolute;
530
+ left: 120px;
531
+ top: 45px;
532
+ width: 200px;
533
+ height: 80px;
534
+ }
535
+ ```
536
+
537
+ #### Coordinate System Difference (CRITICAL for GROUPs)
538
+
539
+ Figma has different coordinate systems for FRAME vs GROUP children:
540
+
541
+ | Parent Type | Child coordinates (`x`, `y`) are relative to... |
542
+ |-------------|--------------------------------------------------|
543
+ | `FRAME` | The frame itself (use directly) |
544
+ | `GROUP` | The containing FRAME, not the group |
545
+
546
+ For GROUP children, you must **subtract the group's position** to get coordinates relative to the group:
547
+
548
+ ```typescript
549
+ const isGroupParent = parentType === 'GROUP';
550
+ const relativeX = isGroupParent && parentBounds
551
+ ? bounds.x - parentBounds.x
552
+ : bounds.x;
553
+ const relativeY = isGroupParent && parentBounds
554
+ ? bounds.y - parentBounds.y
555
+ : bounds.y;
556
+ ```
557
+
558
+ #### Legacy FRAME (No Auto Layout)
559
+
560
+ A FRAME with `layoutMode: NONE` behaves like a GROUP for positioning purposes but uses frame-relative coordinates (no subtraction needed):
561
+
562
+ ```css
563
+ .legacy-frame {
564
+ position: relative;
565
+ overflow: hidden;
566
+ }
567
+
568
+ .legacy-frame__child {
569
+ position: absolute;
570
+ left: 50px;
571
+ top: 30px;
572
+ width: 300px;
573
+ height: 150px;
574
+ }
575
+ ```
576
+
577
+ #### Constraint Mapping
578
+
579
+ Figma's constraint system positions children relative to their parent frame edges. Constraints apply when the parent resizes:
580
+
581
+ | Figma Constraint | CSS Positioning | Description |
582
+ |------------------|-----------------|-------------|
583
+ | `LEFT` | `left: Xpx` | Fixed distance from left edge |
584
+ | `RIGHT` | `right: Xpx` | Fixed distance from right edge |
585
+ | `TOP` | `top: Xpx` | Fixed distance from top edge |
586
+ | `BOTTOM` | `bottom: Xpx` | Fixed distance from bottom edge |
587
+ | `CENTER` | Centered via `left: 50%; transform: translateX(-50%)` | Centered on axis |
588
+ | `SCALE` | Percentage-based positioning | Scales proportionally |
589
+ | `LEFT_RIGHT` | `left: Xpx; right: Ypx` | Stretches horizontally |
590
+ | `TOP_BOTTOM` | `top: Xpx; bottom: Ypx` | Stretches vertically |
591
+
592
+ #### Z-Index in Non-Auto-Layout
593
+
594
+ For absolutely positioned children, later children (higher index in Figma's layer order) render on top:
595
+
596
+ ```css
597
+ .child-0 { z-index: 0; }
598
+ .child-1 { z-index: 1; }
599
+ .child-2 { z-index: 2; }
600
+ ```
601
+
602
+ Z-index is only set when there are multiple children.
603
+
604
+ ---
605
+
606
+ ### 8. Responsive Multi-Frame Pattern
607
+
608
+ Figma does not have built-in responsive breakpoints. The pattern for responsive design is to create **multiple frames** — one for each breakpoint — and merge them into unified CSS with media queries.
609
+
610
+ #### Two Approaches for Responsive Grouping
611
+
612
+ ##### Approach A: #Breakpoint Suffix (Preferred)
613
+
614
+ Frames are grouped by a `#breakpoint` suffix in their name:
615
+
616
+ ```
617
+ Card #mobile → base styles (smallest)
618
+ Card #tablet → @media (min-width: 768px)
619
+ Card #desktop → @media (min-width: 1024px)
620
+ ```
621
+
622
+ Recognized suffix patterns:
623
+ - `"Card - #desktop"` — dash separator
624
+ - `"Card #tablet"` — space separator
625
+ - `"Card(#mobile)"` — parentheses
626
+ - `"Card [#desktop]"` — brackets
627
+
628
+ The `#` prefix is the strict marker. Only `#mobile`, `#tablet`, `#desktop` are recognized (case-insensitive).
629
+
630
+ **Classification rules:**
631
+ - A valid responsive group requires **2+ frames** with the same base name and different `#breakpoint` suffixes
632
+ - Frames without `#breakpoint` suffix are treated as standalone layout frames
633
+ - Orphan variants (only one frame for a base name) are treated as standalone layout frames
634
+ - Duplicate breakpoints within the same group are treated as standalone layout frames
635
+
636
+ ##### Approach B: Variant Component Detection
637
+
638
+ For COMPONENT_SET nodes, responsive variants are detected through variant properties:
639
+
640
+ **Recognized responsive property names** (case-insensitive):
641
+ - `Device`, `Breakpoint`, `Screen`, `Viewport`, `Responsive`, `Size`
642
+
643
+ **Value-to-breakpoint mapping:**
644
+
645
+ | Variant Value | Maps To |
646
+ |---------------|------------|
647
+ | `mobile`, `phone`, `small`, `sm`, `xs` | `mobile` |
648
+ | `tablet`, `medium`, `md` | `tablet` |
649
+ | `desktop`, `large`, `lg`, `xl` | `desktop` |
650
+
651
+ Detection requires at least 2 values that map to different standard breakpoint names.
652
+
653
+ #### Standard Breakpoints (Mobile-First)
654
+
655
+ | Breakpoint | min-width | max-width | CSS |
656
+ |------------|-----------|-----------|-----|
657
+ | `mobile` | _(base)_ | 767px | No media query (base styles) |
658
+ | `tablet` | 768px | 1023px | `@media (min-width: 768px)` |
659
+ | `desktop` | 1024px | _(none)_ | `@media (min-width: 1024px)` |
660
+
661
+ Breakpoint detection also falls back to frame dimensions:
662
+ - Width < 768px → mobile
663
+ - Width 768–1023px → tablet
664
+ - Width >= 1024px → desktop
665
+
666
+ #### Mobile-First CSS Generation
667
+
668
+ The smallest frame provides **base styles** (no media query). Larger frames contribute **override styles** wrapped in `@media (min-width: ...)` blocks:
669
+
670
+ ```css
671
+ /* Base styles from mobile frame */
672
+ .card {
673
+ display: flex;
674
+ flex-direction: column;
675
+ gap: 12px;
676
+ padding: 16px;
677
+ }
678
+
679
+ .card__title {
680
+ font-size: 18px;
681
+ }
682
+
683
+ /* Tablet overrides — only properties that differ */
684
+ @media (min-width: 768px) {
685
+ .card {
686
+ flex-direction: row;
687
+ gap: 24px;
688
+ padding: 24px;
689
+ }
690
+ .card__title {
691
+ font-size: 22px;
692
+ }
693
+ }
694
+
695
+ /* Desktop overrides — only properties that differ from base */
696
+ @media (min-width: 1024px) {
697
+ .card {
698
+ gap: 32px;
699
+ padding: 32px;
700
+ }
701
+ .card__title {
702
+ font-size: 28px;
703
+ }
704
+ }
705
+ ```
706
+
707
+ #### BEM Suffix Matching
708
+
709
+ Elements are matched across breakpoint frames using **BEM class suffix matching**. The BEM block name differs between frames (e.g., `.card---mobile` vs `.card---desktop`), but the element/modifier suffix is identical for elements at the same position:
710
+
711
+ ```
712
+ Mobile frame: .card---mobile → suffix: "" (root)
713
+ .card---mobile__title → suffix: "__title"
714
+ .card---mobile__body → suffix: "__body"
715
+
716
+ Desktop frame: .card---desktop → suffix: "" (root)
717
+ .card---desktop__title → suffix: "__title"
718
+ .card---desktop__body → suffix: "__body"
719
+ ```
720
+
721
+ Matching by suffix: `""` matches `""`, `"__title"` matches `"__title"`, etc.
722
+
723
+ After matching, selectors are remapped to a unified component class: `.card---mobile__title` and `.card---desktop__title` both become `.card__title` in the output.
724
+
725
+ #### Style Diffing and Overrides
726
+
727
+ Only **changed properties** are emitted in media query blocks. The diff algorithm:
728
+
729
+ 1. Compare each property in the larger breakpoint against the base
730
+ 2. Include properties that are new or have different values
731
+ 3. **Reset layout properties** that exist in base but not in the larger breakpoint
732
+
733
+ Layout properties that need explicit resets to prevent cascade leaking:
734
+
735
+ | Property | Reset Value |
736
+ |---------------|-------------|
737
+ | `align-self` | `auto` |
738
+ | `flex-grow` | `0` |
739
+ | `flex-shrink` | `1` |
740
+ | `flex-basis` | `auto` |
741
+
742
+ **Example of reset:**
743
+
744
+ ```css
745
+ /* Mobile base: column layout, child stretches */
746
+ .card__image {
747
+ align-self: stretch;
748
+ flex-grow: 1;
749
+ flex-basis: 0;
750
+ }
751
+
752
+ /* Desktop: row layout, child is fixed — must reset mobile layout props */
753
+ @media (min-width: 1024px) {
754
+ .card__image {
755
+ width: 100%;
756
+ max-width: 277px;
757
+ align-self: auto; /* Reset — no longer stretching */
758
+ flex-grow: 0; /* Reset — no longer filling */
759
+ flex-shrink: 1; /* Reset — back to default */
760
+ flex-basis: auto; /* Reset — back to default */
761
+ }
762
+ }
763
+ ```
764
+
765
+ #### Responsive Width Transformation
766
+
767
+ Fixed pixel widths in responsive overrides are transformed to a fluid pattern to prevent overflow at intermediate viewport widths:
768
+
769
+ ```css
770
+ /* Before transformation */
771
+ .card__sidebar {
772
+ width: 277px;
773
+ }
774
+
775
+ /* After transformation — fluid with cap */
776
+ .card__sidebar {
777
+ width: 100%;
778
+ max-width: 277px;
779
+ }
780
+ ```
781
+
782
+ This only applies to responsive override styles (not base styles) and only when no `max-width` is already explicitly set.
783
+
784
+ ---
785
+
786
+ ### 9. Common Pitfalls & Edge Cases
787
+
788
+ #### Pitfall: Missing `flex-basis: 0` for FILL Items
789
+
790
+ **Problem:** Without `flex-basis: 0`, FILL children distribute space based on content size, not equally.
791
+
792
+ **Rule:** FILL on primary axis always requires both `flex-grow: 1` AND `flex-basis: 0`.
793
+
794
+ ```css
795
+ /* CORRECT */
796
+ .fill-child { flex-grow: 1; flex-basis: 0; }
797
+
798
+ /* WRONG — content-based distribution */
799
+ .fill-child { flex-grow: 1; }
800
+ ```
801
+
802
+ #### Pitfall: STRETCH Only Exists on Counter Axis
803
+
804
+ **Problem:** Trying to apply STRETCH behavior on the primary axis.
805
+
806
+ **Rule:** `STRETCH` / `align-self: stretch` only affects the **counter axis**. On the primary axis, expanding behavior is achieved through FILL (`flex-grow: 1; flex-basis: 0`), not stretch.
807
+
808
+ #### Pitfall: `itemSpacing` Is Not Padding
809
+
810
+ **Problem:** Confusing `itemSpacing` (gap between children) with `padding` (space around children).
811
+
812
+ **Rule:** `itemSpacing` → CSS `gap`. Padding uses the separate `paddingTop/Right/Bottom/Left` properties.
813
+
814
+ #### Pitfall: GROUP Nodes Have No Layout Mode
815
+
816
+ **Problem:** Treating GROUP children as flex items.
817
+
818
+ **Rule:** GROUP nodes never have Auto Layout. All GROUP children must use `position: absolute` with coordinates adjusted for the GROUP's coordinate system (subtract parent position).
819
+
820
+ #### Pitfall: "Auto" in Figma UI Means HUG, Not FILL
821
+
822
+ **Problem:** When Figma's UI shows "Auto" for width/height, it maps to `HUG` (content-based sizing), not `FILL` (expand to fill).
823
+
824
+ **Rule:** "Auto" in the Figma UI → `layoutSizingHorizontal: HUG` or `layoutSizingVertical: HUG`. FILL is shown as "Fill container" in the UI.
825
+
826
+ #### Pitfall: Layout Property Cascade Leaking
827
+
828
+ **Problem:** In responsive CSS, mobile base styles for `flex-grow`, `flex-basis`, `align-self`, and `flex-shrink` leak into larger breakpoints via the cascade when the desktop layout no longer uses those properties.
829
+
830
+ **Rule:** When a layout property exists in the base breakpoint but not in a larger breakpoint, explicitly reset it. See Section 8 for the reset values.
831
+
832
+ #### Edge Case: FILL Counter-Axis + Max Constraint
833
+
834
+ When a child has FILL on the counter-axis and a max-width/max-height constraint, use `width: 100%` / `height: 100%` instead of `align-self: stretch`. This preserves the parent's ability to position the element with `align-items` when the constraint caps its size. See Section 2.5.
835
+
836
+ #### Edge Case: Container primaryAxisSizing and counterAxisSizing
837
+
838
+ The Auto Layout container itself has sizing modes for its own dimensions:
839
+
840
+ | Property | Value | Meaning |
841
+ |----------|-------|---------|
842
+ | `primaryAxisSizingMode` | `FIXED` | Container has explicit size on primary axis |
843
+ | `primaryAxisSizingMode` | `AUTO` | Container hugs its content on primary axis |
844
+ | `counterAxisSizingMode` | `FIXED` | Container has explicit size on counter axis |
845
+ | `counterAxisSizingMode` | `AUTO` | Container hugs its content on counter axis |
846
+
847
+ These determine whether the container itself gets an explicit width/height or sizes to content. They are separate from the child-level `layoutSizingHorizontal`/`layoutSizingVertical` properties.
848
+
849
+ #### Edge Case: Variable Fallback Strategy
850
+
851
+ When a Figma Variable is from an **external library** (not local to the file), include a pixel fallback in the `var()` expression:
852
+
853
+ ```css
854
+ /* Local variable — no fallback */
855
+ gap: var(--spacing-md);
856
+
857
+ /* External library variable — include fallback */
858
+ gap: var(--spacing-md, 16px);
859
+ ```
860
+
861
+ This ensures the CSS works even if the variable definition is not available in the consuming project.
862
+
863
+ ---
864
+
865
+ ### Intermediate Type Reference
866
+
867
+ The complete intermediate types used between extraction and generation:
868
+
869
+ ```typescript
870
+ interface LayoutProperties {
871
+ mode: 'NONE' | 'FLEX_ROW' | 'FLEX_COL' | 'GRID';
872
+ primaryAxisSizing: 'FIXED' | 'AUTO';
873
+ counterAxisSizing: 'FIXED' | 'AUTO';
874
+ justify: 'flex-start' | 'flex-end' | 'center' | 'space-between';
875
+ align: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch';
876
+ gap: number;
877
+ gapVariable?: VariableReference;
878
+ counterAxisGap?: number;
879
+ counterAxisGapVariable?: VariableReference;
880
+ padding: {
881
+ top: number;
882
+ right: number;
883
+ bottom: number;
884
+ left: number;
885
+ };
886
+ paddingVariables?: {
887
+ top?: VariableReference;
888
+ right?: VariableReference;
889
+ bottom?: VariableReference;
890
+ left?: VariableReference;
891
+ };
892
+ wrap: boolean;
893
+ wrapAlign?: 'flex-start' | 'space-between';
894
+ minWidth?: number;
895
+ maxWidth?: number;
896
+ minHeight?: number;
897
+ maxHeight?: number;
898
+ }
899
+
900
+ interface LayoutChildProperties {
901
+ flexGrow: number;
902
+ alignSelf: 'auto' | 'flex-start' | 'center' | 'flex-end' | 'stretch';
903
+ sizingHorizontal: 'FIXED' | 'HUG' | 'FILL';
904
+ sizingVertical: 'FIXED' | 'HUG' | 'FILL';
905
+ minWidth?: number;
906
+ maxWidth?: number;
907
+ minHeight?: number;
908
+ maxHeight?: number;
909
+ }
910
+
911
+ interface VariableReference {
912
+ id: string;
913
+ name: string;
914
+ isLocal: boolean;
915
+ }
916
+ ```
917
+
918
+ ---
919
+
920
+ ## Cross-References
921
+
922
+ - **`figma-api-rest.md`** — Node tree structure, `absoluteBoundingBox` for bounds, file/node fetching for accessing layout data via REST API
923
+ - **`figma-api-plugin.md`** — SceneNode types (FrameNode, ComponentNode, InstanceNode, GroupNode), LayoutMixin properties (`layoutMode`, `layoutSizingHorizontal`, `layoutAlign`, `itemSpacing`, etc.), plugin sandbox model for extraction code
924
+ - **`figma-api-variables.md`** — Variables API for resolving `boundVariables` references, variable collections, modes, and the `figma.variables.getVariableByIdAsync()` method used in extraction
925
+ - **`design-to-code-visual.md`** — Visual properties (fills, strokes, effects) that combine with layout for complete component CSS; visual and layout styles are generated independently and merged
926
+ - **`design-to-code-typography.md`** — Text properties that interact with sizing modes (text auto-resize vs layout sizing), vertical alignment requiring flex, styled segments
927
+ - **`design-to-code-assets.md`** — Vector container detection interacts with Auto Layout detection (Auto Layout overrides vector container), asset nodes skip layout child sizing
928
+ - **`design-to-code-semantic.md`** — BEM naming conventions used in responsive matching and class generation, semantic tag selection that consumes layout context
929
+ - **`css-strategy.md`** — Layered CSS approach (Tailwind for layout bones, CSS Modules for visual skin) that consumes layout CSS output. Property placement decision tree for layout properties (Layer 1).