@sxl-studio/token-transformer 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 (43) hide show
  1. package/README.en.md +638 -0
  2. package/README.md +715 -0
  3. package/config.example.json +45 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +160 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/loader.d.ts +8 -0
  8. package/dist/core/loader.js +105 -0
  9. package/dist/core/loader.js.map +1 -0
  10. package/dist/core/parser.d.ts +5 -0
  11. package/dist/core/parser.js +186 -0
  12. package/dist/core/parser.js.map +1 -0
  13. package/dist/core/types.d.ts +83 -0
  14. package/dist/core/types.js +2 -0
  15. package/dist/core/types.js.map +1 -0
  16. package/dist/core/writer.d.ts +6 -0
  17. package/dist/core/writer.js +124 -0
  18. package/dist/core/writer.js.map +1 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.js +7 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/transformers/css.d.ts +2 -0
  23. package/dist/transformers/css.js +485 -0
  24. package/dist/transformers/css.js.map +1 -0
  25. package/dist/transformers/kotlin.d.ts +2 -0
  26. package/dist/transformers/kotlin.js +445 -0
  27. package/dist/transformers/kotlin.js.map +1 -0
  28. package/dist/transformers/swiftui.d.ts +2 -0
  29. package/dist/transformers/swiftui.js +436 -0
  30. package/dist/transformers/swiftui.js.map +1 -0
  31. package/dist/transformers/vue3.d.ts +28 -0
  32. package/dist/transformers/vue3.js +534 -0
  33. package/dist/transformers/vue3.js.map +1 -0
  34. package/dist/utils/color.d.ts +11 -0
  35. package/dist/utils/color.js +101 -0
  36. package/dist/utils/color.js.map +1 -0
  37. package/dist/utils/dimension.d.ts +7 -0
  38. package/dist/utils/dimension.js +62 -0
  39. package/dist/utils/dimension.js.map +1 -0
  40. package/dist/utils/naming.d.ts +13 -0
  41. package/dist/utils/naming.js +58 -0
  42. package/dist/utils/naming.js.map +1 -0
  43. package/package.json +40 -0
package/README.en.md ADDED
@@ -0,0 +1,638 @@
1
+ # SXL Studio — Token Transformer
2
+
3
+ A tool for transforming design tokens from JSON (DTCG format) into valid code for three platforms:
4
+ - **Web** — CSS Custom Properties
5
+ - **iOS** — SwiftUI
6
+ - **Android** — Kotlin Compose
7
+ - **Vue 3** — Composition JSON → Vue 3 SFC (`.vue`)
8
+
9
+ ---
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Install dependencies
15
+ npm install
16
+
17
+ # Run with default config
18
+ npx tsx src/cli.ts
19
+
20
+ # Run with specific config
21
+ npx tsx src/cli.ts --config=config.example.json
22
+
23
+ # Generate a default config
24
+ npx tsx src/cli.ts --init
25
+
26
+ # Transform composition → Vue 3
27
+ npx tsx src/cli.ts --composition=path/to/composition.json --output=./output/dir
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Project Structure
33
+
34
+ ```
35
+ Transformer/
36
+ ├── src/
37
+ │ ├── cli.ts # CLI entry point
38
+ │ ├── core/
39
+ │ │ ├── types.ts # Types and interfaces
40
+ │ │ ├── parser.ts # JSON parsing, alias resolution, math expressions
41
+ │ │ ├── loader.ts # Token loading from files
42
+ │ │ └── writer.ts # Transformation and file writing
43
+ │ ├── transformers/
44
+ │ │ ├── css.ts # CSS Custom Properties generator
45
+ │ │ ├── swiftui.ts # SwiftUI code generator
46
+ │ │ ├── kotlin.ts # Kotlin Compose code generator
47
+ │ │ └── vue3.ts # Composition JSON → Vue 3 SFC
48
+ │ └── utils/
49
+ │ ├── naming.ts # Naming utilities (kebab, camel, pascal)
50
+ │ ├── color.ts # Color parsing and formatting
51
+ │ └── dimension.ts # Dimension parsing and formatting
52
+ ├── config.example.json # Config for test tokens
53
+ ├── config.admin-ui.json # Config for admin-ui project
54
+ ├── config.site-ui.json # Config for site-ui project
55
+ ├── project-style/ # Output files (generated)
56
+ └── package.json
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Configuration
62
+
63
+ The config is a JSON file with the following structure:
64
+
65
+ ### Full Schema
66
+
67
+ ```json
68
+ {
69
+ "source": {
70
+ "tokenDir": "../Plugin/tokens",
71
+ "configFile": "config.json",
72
+ "include": ["example/*.json", "core/*.json"],
73
+ "exclude": ["config.json", "**/diff-id*.json", "**/composition*"]
74
+ },
75
+ "platforms": {
76
+ "css": {
77
+ "outputDir": "./project-style/example",
78
+ "prefix": "ds",
79
+ "resolveAliases": false,
80
+ "splitEffects": true,
81
+ "showDescriptions": true,
82
+ "codeSyntax": {
83
+ "colorFormat": "hex",
84
+ "prefix": "ds"
85
+ },
86
+ "fileMapping": [
87
+ {
88
+ "sources": ["example/*.json"],
89
+ "output": "all-tokens.css",
90
+ "filter": {
91
+ "types": ["color", "dimension"],
92
+ "paths": ["color.", "spacing."],
93
+ "excludePaths": ["color.internal."]
94
+ }
95
+ }
96
+ ]
97
+ },
98
+ "swiftui": {
99
+ "outputDir": "./project-style/example",
100
+ "prefix": "DS",
101
+ "resolveAliases": true,
102
+ "showDescriptions": true,
103
+ "fileMapping": [
104
+ {
105
+ "sources": ["example/*.json"],
106
+ "output": "AllTokens.swift"
107
+ }
108
+ ]
109
+ },
110
+ "kotlin": {
111
+ "outputDir": "./project-style/example",
112
+ "prefix": "DS",
113
+ "resolveAliases": true,
114
+ "showDescriptions": true,
115
+ "fileMapping": [
116
+ {
117
+ "sources": ["example/*.json"],
118
+ "output": "AllTokens.kt"
119
+ }
120
+ ]
121
+ }
122
+ },
123
+ "settings": {
124
+ "remBase": 16,
125
+ "verbose": false
126
+ }
127
+ }
128
+ ```
129
+
130
+ ---
131
+
132
+ ### Field Descriptions
133
+
134
+ #### `source` — Token Source
135
+
136
+ | Field | Type | Default | Description |
137
+ |-------|------|---------|-------------|
138
+ | `tokenDir` | `string` | required | Path to the JSON token directory |
139
+ | `configFile` | `string` | `"config.json"` | Path to plugin config.json (inside tokenDir) |
140
+ | `include` | `string[]` | all `**/*.json` | Glob patterns for files to include |
141
+ | `exclude` | `string[]` | `["config.json"]` | Glob patterns for files to exclude |
142
+
143
+ #### `platforms` — Platform Settings
144
+
145
+ Each platform (`css`, `swiftui`, `kotlin`) is configured independently:
146
+
147
+ | Field | Type | Default | Description |
148
+ |-------|------|---------|-------------|
149
+ | `outputDir` | `string` | required | Output directory for generated files |
150
+ | `prefix` | `string` | `""` | Prefix for variable names |
151
+ | `resolveAliases` | `boolean` | CSS: `false`, Swift/Kotlin: `true` | Whether to resolve aliases to final values |
152
+ | `splitEffects` | `boolean` | `true` | Split effects into separate variables (CSS only) |
153
+ | `showDescriptions` | `boolean` | `true` | Include `$description` as comments in output |
154
+ | `codeSyntax` | `object` | — | Code format settings |
155
+ | `fileMapping` | `array` | — | Rules for merging tokens into files |
156
+
157
+ #### `resolveAliases` — Alias Control
158
+
159
+ The key setting that determines how alias references (`{token.path}`) are handled in composite tokens.
160
+
161
+ **CSS (`resolveAliases: false` — default):**
162
+ ```css
163
+ /* Aliases preserved as var() */
164
+ --typography-heading-xl: var(--font-weight-bold) var(--font-size-3xl)/var(--line-height-relaxed) var(--font-family-sans);
165
+ --shadow-sm: var(--number-fx-offset-xs) var(--number-fx-offset-none) var(--number-fx-blur-xs) var(--number-fx-offset-none) var(--color-hex-alpha);
166
+ ```
167
+
168
+ **CSS (`resolveAliases: true`):**
169
+ ```css
170
+ /* All values fully resolved */
171
+ --ds-typography-heading-xl: 700 30px/24px Inter;
172
+ --ds-shadow-sm: 2px 0px 8px 0px rgba(255, 85, 0, 0.502);
173
+ ```
174
+
175
+ **SwiftUI/Kotlin (`resolveAliases: false`):**
176
+ - Pure aliases (entire token = reference) → same-scope constant reference
177
+ - Composite tokens with aliased fields → `/// @ref` / `/** @ref */` comments with paths
178
+
179
+ ```swift
180
+ /// @ref {color.hex-full}
181
+ static let primary = hexFull // ← direct reference
182
+
183
+ /// @ref {fontFamily.sans}, {fontWeight.bold}, {fontSize.3xl}, {lineHeight.relaxed}
184
+ static let headingXl: Font = .system(size: 30, weight: .bold) // ← values + ref comment
185
+ ```
186
+
187
+ **SwiftUI/Kotlin (`resolveAliases: true` — default):**
188
+ ```swift
189
+ static let primary = Color(red: 1, green: 0.3333, blue: 0, opacity: 1) // ← fully resolved
190
+ ```
191
+
192
+ #### `splitEffects` — Effects Splitting (CSS only)
193
+
194
+ Controls how mixed effects (shadow + blur + backdrop-blur) are handled.
195
+
196
+ **`splitEffects: true` (default):**
197
+
198
+ Mixed effects are split into separate variables with suffixes. Each variable maps to a specific CSS property:
199
+
200
+ ```css
201
+ /* box-shadow */
202
+ --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
203
+ /* filter */
204
+ --effects-full-mix-blur: blur(2px);
205
+ /* backdrop-filter */
206
+ --effects-full-mix-backdrop-blur: blur(20px);
207
+ ```
208
+
209
+ Usage:
210
+ ```css
211
+ .card {
212
+ box-shadow: var(--effects-full-mix);
213
+ filter: var(--effects-full-mix-blur);
214
+ backdrop-filter: var(--effects-full-mix-backdrop-blur);
215
+ }
216
+ ```
217
+
218
+ **`splitEffects: false`:**
219
+
220
+ All effects are output as a single variable, shadow part only:
221
+
222
+ ```css
223
+ --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
224
+ ```
225
+
226
+ #### `showDescriptions` — Token Descriptions
227
+
228
+ Controls whether `$description` from JSON is included as comments. Works on all platforms.
229
+
230
+ **`showDescriptions: true` (default):**
231
+ ```css
232
+ /* Heading XL — all values raw */
233
+ --typography-heading-xl: 700 30px/24px Inter;
234
+ ```
235
+
236
+ ```swift
237
+ /// Heading XL — all values raw
238
+ static let headingXl: Font = .system(size: 30, weight: .bold)
239
+ ```
240
+
241
+ ```kotlin
242
+ /** Heading XL — all values raw */
243
+ val HeadingXl = TextStyle(...)
244
+ ```
245
+
246
+ **`showDescriptions: false`:**
247
+ ```css
248
+ --typography-heading-xl: 700 30px/24px Inter;
249
+ ```
250
+
251
+ ```swift
252
+ static let headingXl: Font = .system(size: 30, weight: .bold)
253
+ ```
254
+
255
+ ```kotlin
256
+ val HeadingXl = TextStyle(...)
257
+ ```
258
+
259
+ #### `fileMapping` — File Merging Rules
260
+
261
+ Allows combining tokens from multiple JSON files into a single output file.
262
+
263
+ ```json
264
+ {
265
+ "sources": ["core/palette.json", "projects/aui/colors.json", "projects/modes/themes/light.json"],
266
+ "output": "core.css",
267
+ "filter": {
268
+ "types": ["color", "dimension"],
269
+ "paths": ["color.primary"],
270
+ "excludePaths": ["color.internal"]
271
+ }
272
+ }
273
+ ```
274
+
275
+ | Field | Description |
276
+ |-------|-------------|
277
+ | `sources` | Array of JSON file paths (supports `*` wildcards) |
278
+ | `output` | Output file name (relative to `outputDir`) |
279
+ | `filter.types` | Filter by token type |
280
+ | `filter.paths` | Include only paths starting with specified prefixes |
281
+ | `filter.excludePaths` | Exclude paths starting with specified prefixes |
282
+
283
+ #### `settings` — Global Settings
284
+
285
+ | Field | Type | Default | Description |
286
+ |-------|------|---------|-------------|
287
+ | `remBase` | `number` | `16` | Base value for rem calculations |
288
+ | `verbose` | `boolean` | `false` | Verbose logging output |
289
+
290
+ ---
291
+
292
+ ## Supported Token Types
293
+
294
+ ### Simple Types
295
+
296
+ | Type | CSS | SwiftUI | Kotlin |
297
+ |------|-----|---------|--------|
298
+ | `color` | `#hex` / `rgba()` | `Color(red:green:blue:opacity:)` | `Color(0xAARRGGBB)` |
299
+ | `dimension` | `16px` / `1rem` | `CGFloat` | `Dp` |
300
+ | `spacing`, `sizing` | `16px` | `CGFloat` | `Dp` |
301
+ | `borderRadius`, `borderWidth` | `4px` | `CGFloat` | `Dp` |
302
+ | `opacity` | `0.5` | `Double` | `Float` |
303
+ | `number` | `42` | numeric | numeric |
304
+ | `fontFamily` | `"Inter"` | `String` | `String` |
305
+ | `fontWeight` | `700` | `Font.Weight` | `FontWeight` |
306
+ | `fontSize`, `lineHeight` | `16px` | `CGFloat` | `TextUnit (sp)` |
307
+ | `letterSpacing` | `0.5px` | `CGFloat` | `TextUnit (sp)` |
308
+ | `duration` | `200ms` | `String` | `String` |
309
+ | `cubicBezier` | `cubic-bezier(...)` | `String` | `String` |
310
+ | `boolean` | `true` | `Bool` | `Boolean` |
311
+ | `text`, `string` | `"value"` | `String` | `String` |
312
+ | `textCase` | `uppercase` | `String` | `String` |
313
+ | `textDecoration` | `underline` | `String` | `String` |
314
+ | `strokeStyle` | `dashed /* ... */` | `String` | `String` |
315
+
316
+ ### Composite Types
317
+
318
+ | Type | CSS | SwiftUI | Kotlin |
319
+ |------|-----|---------|--------|
320
+ | `typography` | `700 16px/24px Inter` | `Font.system(size:weight:)` | `TextStyle(...)` |
321
+ | `shadow` | `0 4px 8px rgba(...)` | `.shadow(color:radius:x:y:)` | `elevation (Dp)` |
322
+ | `border` | `1px solid #000` | `(color:width:style:)` | `BorderStroke(...)` |
323
+ | `fill` | `#hex` / `gradient(...)` | `Color(...)` / `Gradient(...)` | `Color(...)` / `Brush(...)` |
324
+ | `gradient` | `linear-gradient(...)` | `LinearGradient(...)` | `Brush.linearGradient(...)` |
325
+ | `effects` | see below | `.shadow(...)` | `elevation (Dp)` |
326
+ | `blur` | `blur(4px)` | `.blur(radius:)` | `Modifier.blur(...)` |
327
+ | `backdrop-blur` | `blur(16px)` | `.blur(radius:)` | `Modifier.blur(...)` |
328
+ | `transition` | `all 200ms ease` | `String` | `String` |
329
+ | `grid` | `repeat(12, 1fr)` | `String` | `String` |
330
+
331
+ ---
332
+
333
+ ## Effects Handling (CSS)
334
+
335
+ In CSS, different effect types map to different CSS properties. The transformer automatically creates separate variables for each type:
336
+
337
+ ### Single Effect Type
338
+
339
+ ```css
340
+ /* box-shadow */
341
+ --effects-card: 0 4px 8px rgba(0,0,0,0.1);
342
+
343
+ /* filter */
344
+ --blur-sm: blur(4px);
345
+
346
+ /* backdrop-filter */
347
+ --backdrop-blur-md: blur(12px);
348
+ ```
349
+
350
+ ### Mixed Effects (shadow + blur + backdrop-blur)
351
+
352
+ When a token contains multiple effect types, they are split into separate variables with suffixes:
353
+
354
+ ```css
355
+ /* box-shadow */
356
+ --effects-full-mix: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
357
+ /* filter */
358
+ --effects-full-mix-blur: blur(2px);
359
+ /* backdrop-filter */
360
+ --effects-full-mix-backdrop-blur: blur(20px);
361
+ ```
362
+
363
+ Usage in CSS:
364
+ ```css
365
+ .card {
366
+ box-shadow: var(--effects-full-mix);
367
+ filter: var(--effects-full-mix-blur);
368
+ backdrop-filter: var(--effects-full-mix-backdrop-blur);
369
+ }
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Aliases in Composite Tokens
375
+
376
+ All composite token types (typography, shadow, border, fill, gradient, effects) support alias references `{token.path}` in any sub-property.
377
+
378
+ ### JSON Example
379
+
380
+ ```json
381
+ {
382
+ "heading": {
383
+ "xl": {
384
+ "$type": "typography",
385
+ "$value": {
386
+ "fontFamily": "{fontFamily.sans}",
387
+ "fontWeight": "{fontWeight.bold}",
388
+ "fontSize": "{fontSize.3xl}",
389
+ "lineHeight": "{lineHeight.relaxed}"
390
+ }
391
+ }
392
+ }
393
+ }
394
+ ```
395
+
396
+ ### CSS Output (resolveAliases: false)
397
+
398
+ ```css
399
+ --typography-heading-xl: var(--font-weight-bold) var(--font-size-3xl)/var(--line-height-relaxed) var(--font-family-sans);
400
+ ```
401
+
402
+ ### CSS Output (resolveAliases: true)
403
+
404
+ ```css
405
+ --typography-heading-xl: 700 30px/24px Inter;
406
+ ```
407
+
408
+ ---
409
+
410
+ ## codeSyntax (Figma)
411
+
412
+ If a token has `$extensions.figma.codeSyntax`, the transformer uses the specified variable names:
413
+
414
+ ```json
415
+ {
416
+ "primary": {
417
+ "$value": "#0066FF",
418
+ "$extensions": {
419
+ "figma.codeSyntax": {
420
+ "Web": "var(--color-primary)",
421
+ "iOS": "Color.primary",
422
+ "Android": "@color/primary"
423
+ }
424
+ }
425
+ }
426
+ }
427
+ ```
428
+
429
+ ---
430
+
431
+ ## Math Expressions
432
+
433
+ Tokens support mathematical expressions that are evaluated during transformation:
434
+
435
+ ```json
436
+ {
437
+ "spacing": {
438
+ "sm": { "$value": "{spacing.base} * 2" },
439
+ "md": { "$value": "{spacing.base} * 4" }
440
+ }
441
+ }
442
+ ```
443
+
444
+ Supported operations: `+`, `-`, `*`, `/`, `round()`.
445
+
446
+ Expressions are evaluated recursively, including sub-properties of composite tokens.
447
+
448
+ ---
449
+
450
+ ## Usage Examples
451
+
452
+ ### Transform All Projects
453
+
454
+ ```bash
455
+ # Test tokens (example)
456
+ npx tsx src/cli.ts --config=config.example.json
457
+
458
+ # Production projects
459
+ npx tsx src/cli.ts --config=config.admin-ui.json
460
+ npx tsx src/cli.ts --config=config.site-ui.json
461
+ ```
462
+
463
+ ### Example Config for a New Project
464
+
465
+ ```json
466
+ {
467
+ "source": {
468
+ "tokenDir": "./path/to/tokens",
469
+ "exclude": ["config.json"]
470
+ },
471
+ "platforms": {
472
+ "css": {
473
+ "outputDir": "./dist/css",
474
+ "prefix": "my",
475
+ "resolveAliases": false,
476
+ "splitEffects": true,
477
+ "showDescriptions": true,
478
+ "fileMapping": [
479
+ {
480
+ "sources": ["core/*.json", "themes/light.json"],
481
+ "output": "core.css"
482
+ },
483
+ {
484
+ "sources": ["themes/dark.json"],
485
+ "output": "themes/dark.css"
486
+ }
487
+ ]
488
+ },
489
+ "swiftui": {
490
+ "outputDir": "./dist/ios",
491
+ "prefix": "My",
492
+ "resolveAliases": false,
493
+ "showDescriptions": true,
494
+ "fileMapping": [
495
+ {
496
+ "sources": ["core/*.json", "themes/light.json"],
497
+ "output": "MyTokens.swift"
498
+ }
499
+ ]
500
+ },
501
+ "kotlin": {
502
+ "outputDir": "./dist/android",
503
+ "prefix": "My",
504
+ "resolveAliases": true,
505
+ "showDescriptions": false,
506
+ "fileMapping": [
507
+ {
508
+ "sources": ["core/*.json", "themes/light.json"],
509
+ "output": "MyTokens.kt"
510
+ }
511
+ ]
512
+ }
513
+ },
514
+ "settings": {
515
+ "remBase": 16
516
+ }
517
+ }
518
+ ```
519
+
520
+ ---
521
+
522
+ ## Composition → Vue 3 (SFC)
523
+
524
+ The transformer supports converting composition tokens from JSON into ready-to-use Vue 3 Single File Components (`.vue`).
525
+
526
+ ### Usage
527
+
528
+ ```bash
529
+ npx tsx src/cli.ts --composition=path/to/composition.json --output=./output/dir
530
+ ```
531
+
532
+ | Parameter | Description |
533
+ |-----------|-------------|
534
+ | `--composition` | Path to the composition JSON file |
535
+ | `--output` | Output directory for the `.vue` file (defaults to source directory) |
536
+
537
+ ### Input JSON Format
538
+
539
+ ```json
540
+ {
541
+ "$type": "composition",
542
+ "name": "WButton",
543
+ "props": {
544
+ "state": ["default", "hover", "active", "disabled"],
545
+ "style": ["accent", "secondary", "tertiary"],
546
+ "size": ["sm", "md", "lg"]
547
+ },
548
+ "structure": { "tag": "FRAME", "class": "btn", "children": [...] },
549
+ "componentProperties": { "label": { "type": "TEXT", "layer": "btn-label", "defaultValue": "Button" } },
550
+ "styles": { "btn": { "layoutMode": "HORIZONTAL", "fills": ["{accent.medium}"], ... } },
551
+ "adapters": { "style=accent": { "btn": { "fills": ["{info.medium}"] } } }
552
+ }
553
+ ```
554
+
555
+ ### What Gets Generated
556
+
557
+ A complete Vue 3 SFC with three sections:
558
+
559
+ **`<template>`** — HTML structure from `structure`:
560
+ - `FRAME` → `<div>` / `<button>` (inferred from component name)
561
+ - `TEXT` → `<span>` with prop or slot binding
562
+ - `INSTANCE` → component tag (`<WIcon>`, etc.)
563
+ - `BOOLEAN` properties → `v-if` directives
564
+ - `INSTANCE_SWAP` → dynamic `:name` prop
565
+
566
+ **`<script setup lang="ts">`** — logic:
567
+ - `defineProps<Props>()` with types from `props` and `componentProperties`
568
+ - `withDefaults()` with default values
569
+ - `useCssModule()` for CSS Modules
570
+ - `computed()` for variant/size classes with exhaustive `Record<>` mapping
571
+ - `style` prop auto-renamed to `variant` (Vue reserved word conflict)
572
+ - `state=disabled` → separate `disabled` boolean prop
573
+
574
+ **`<style module>`** — CSS:
575
+ - Base styles from `styles` with Figma → CSS property mapping
576
+ - Modifier classes from `adapters` (`.btn--accent`, `.btn--sm`, etc.)
577
+ - `state=hover/active/focus` → CSS pseudo-classes (`:hover`, `:active`, `:focus`)
578
+ - `state=disabled` → `.btn--disabled` + `:disabled`
579
+ - Token references `{accent.medium}` → `var(--accent-medium)`
580
+ - TEXT/INSTANCE layers: `fills` → `color` (not `background-color`)
581
+
582
+ ### Figma → CSS Property Mapping
583
+
584
+ | Figma | CSS |
585
+ |-------|-----|
586
+ | `layoutMode: "HORIZONTAL"` | `display: flex` |
587
+ | `layoutMode: "VERTICAL"` | `display: flex; flex-direction: column` |
588
+ | `primaryAxisAlignItems` | `justify-content` |
589
+ | `counterAxisAlignItems` | `align-items` |
590
+ | `layoutSizingHorizontal: "HUG"` | `width: fit-content` |
591
+ | `layoutSizingHorizontal: "FILL"` | `flex: 1` |
592
+ | `itemSpacing` | `gap` |
593
+ | `padding*` | `padding` (shorthand) |
594
+ | `cornerRadius` | `border-radius` |
595
+ | `fills: ["{token}"]` | `background-color: var(--token)` / `color: var(--token)` |
596
+ | `strokeWeight` + `strokes` | `border` |
597
+ | `opacity` | `opacity` |
598
+ | `fontSize` | `font-size` |
599
+ | `fontWeight` | `font-weight` (name → number mapping) |
600
+
601
+ ---
602
+
603
+ ## Prefix Reference
604
+
605
+ | Platform | Prefix | Naming Result |
606
+ |----------|--------|---------------|
607
+ | CSS | `""` | `--color-primary` |
608
+ | CSS | `"ds"` | `--ds-color-primary` |
609
+ | SwiftUI | `""` | `Color.colorPrimary`, `enum Spacing` |
610
+ | SwiftUI | `"DS"` | `Color.dsColorPrimary`, `enum DSSpacing` |
611
+ | Kotlin | `""` | `DSColors.ColorPrimary`, `DSSpacing.SpacingXs` |
612
+ | Kotlin | `"App"` | `AppColors.ColorPrimary`, `AppSpacing.SpacingXs` |
613
+
614
+ ---
615
+
616
+ ## Troubleshooting
617
+
618
+ ### Config file not found
619
+ ```
620
+ Config file not found: ...
621
+ Run with --init to generate a default config.
622
+ ```
623
+ Use `--config=path/to/file.json` or `--init` to generate one.
624
+
625
+ ### Tokens missing from output
626
+ Check `fileMapping.sources` — paths are **relative to tokenDir**.
627
+
628
+ ### `var()` not appearing in CSS
629
+ Make sure `"resolveAliases": false` is set for the `css` platform.
630
+
631
+ ### Aliases not resolving
632
+ Ensure the source token exists in the included files (`include` / `sources`).
633
+
634
+ ### Effects not splitting into separate variables
635
+ Make sure `"splitEffects": true` is set for the `css` platform. Default is `true`.
636
+
637
+ ### Descriptions ($description) not showing
638
+ Make sure `"showDescriptions": true` is set for the desired platform. Default is `true`.