Tokens to Code
Code generation from design tokens: CSS Custom Properties, SwiftUI, Kotlin Compose, Vue 3 SFC. Configuration, examples, and Transformer setup.
Why Tokens to Code
Design tokens are not just about Figma. Their ultimate purpose is to become working code on the developer's side. Tokens to Code takes your JSON files and turns them into ready-to-use code for three platforms:
- CSS — Custom Properties (
--color-primary: #0066FF;) - SwiftUI — extensions and constants (
static let primary = Color(...)) - Kotlin Compose — objects and types (
val Primary = Color(0xFF0066FF))
Additionally, Vue 3 SFC generation from Composition tokens is available.
Two ways to use it
- From the plugin — Download tab → Tokens to Code. Select platforms, configure settings, download a ZIP archive with generated code
- Via CLI (Transformer) — a command-line utility for CI/CD pipeline integration or local builds
Using in the plugin
Interface
In the Download tab, select the Tokens to Code section. Available settings:
| Setting | Description |
|---|---|
| Platforms | Platforms to generate for (CSS, SwiftUI, Kotlin) |
| Prefix | Variable name prefix (ds → --ds-color-primary) |
| Resolve Aliases | Whether to resolve aliases to final values or keep them as references |
| Split Effects | Split mixed effects into separate variables (CSS only) |
| Show Descriptions | Include $description as code comments |
Result
The plugin generates a ZIP archive tokens-transformed.zip with files grouped by collections and modes from config.json.
Supported token types
Simple types
| Token type | CSS | SwiftUI | Kotlin Compose |
|---|---|---|---|
color | #hex / rgba() | Color(red:green:blue:opacity:) | Color(0xAARRGGBB) |
dimension | 16px / 1rem | CGFloat | Dp |
spacing, sizing | 16px | CGFloat | Dp |
borderRadius, borderWidth | 4px | CGFloat | Dp |
opacity | 0.5 | Double | Float |
number | 42 | numeric value | numeric value |
fontFamily | "Inter" | String | String |
fontWeight | 700 | Font.Weight | FontWeight |
fontSize, lineHeight | 16px | CGFloat | TextUnit (sp) |
letterSpacing | 0.5px | CGFloat | TextUnit (sp) |
duration | 200ms | String | String |
cubicBezier | cubic-bezier(...) | String | String |
boolean | true | Bool | Boolean |
text, string | "value" | String | String |
textCase | uppercase | String | String |
textDecoration | underline | String | String |
Composite types
| Token type | CSS | SwiftUI | Kotlin Compose |
|---|---|---|---|
typography | 700 16px/24px Inter | Font.system(size:weight:) | TextStyle(...) |
shadow | 0 4px 8px rgba(...) | .shadow(color:radius:x:y:) | elevation (Dp) |
border | 1px solid #000 | (color:width:style:) | BorderStroke(...) |
fill | #hex / gradient(...) | Color(...) / Gradient(...) | Color(...) / Brush(...) |
gradient | linear-gradient(...) | LinearGradient(...) | Brush.linearGradient(...) |
effects | box-shadow + filter | .shadow(...) | elevation (Dp) |
blur | blur(4px) | .blur(radius:) | Modifier.blur(...) |
backdrop-blur | blur(16px) | .blur(radius:) | Modifier.blur(...) |
transition | all 200ms ease | String | String |
grid | repeat(12, 1fr) | String | String |
Output examples
CSS Custom Properties
Source JSON:
{
"color": {
"primary": {
"$type": "color",
"$value": "#0066FF",
"$description": "Primary brand color"
},
"danger": {
"$type": "color",
"$value": "#FF3B30"
}
},
"spacing": {
"sm": { "$type": "spacing", "$value": "8px" },
"md": { "$type": "spacing", "$value": "16px" },
"lg": { "$type": "spacing", "$value": "24px" }
},
"typography": {
"heading": {
"xl": {
"$type": "typography",
"$value": {
"fontFamily": "{fontFamily.sans}",
"fontWeight": "{fontWeight.bold}",
"fontSize": "{fontSize.3xl}",
"lineHeight": "{lineHeight.relaxed}"
},
"$description": "Extra large heading"
}
}
}
}
CSS result (resolveAliases: false):
:root {
/* Primary brand color */
--color-primary: #0066FF;
--color-danger: #FF3B30;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
/* Extra large heading */
--typography-heading-xl: var(--font-weight-bold) var(--font-size-3xl)/var(--line-height-relaxed) var(--font-family-sans);
}
CSS result (resolveAliases: true, prefix: "ds"):
:root {
/* Primary brand color */
--ds-color-primary: #0066FF;
--ds-color-danger: #FF3B30;
--ds-spacing-sm: 8px;
--ds-spacing-md: 16px;
--ds-spacing-lg: 24px;
/* Extra large heading */
--ds-typography-heading-xl: 700 30px/24px Inter;
}
SwiftUI
Result (resolveAliases: false):
import SwiftUI
// MARK: - Colors
extension Color {
/// Primary brand color
static let colorPrimary = Color(red: 0, green: 0.4, blue: 1, opacity: 1)
static let colorDanger = Color(red: 1, green: 0.2314, blue: 0.1882, opacity: 1)
}
// MARK: - Spacing
enum Spacing {
static let sm: CGFloat = 8
static let md: CGFloat = 16
static let lg: CGFloat = 24
}
// MARK: - Typography
extension Font {
/// Extra large heading
/// @ref {fontFamily.sans}, {fontWeight.bold}, {fontSize.3xl}, {lineHeight.relaxed}
static let typographyHeadingXl: Font = .system(size: 30, weight: .bold)
}
Kotlin Compose
Result (resolveAliases: true):
package design.tokens
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
/** Primary brand color */
object DSColors {
val ColorPrimary = Color(0xFF0066FF)
val ColorDanger = Color(0xFFFF3B30)
}
object DSSpacing {
val Sm = 8.dp
val Md = 16.dp
val Lg = 24.dp
}
object DSTypography {
/** Extra large heading */
val HeadingXl = TextStyle(
fontSize = 30.sp,
fontWeight = FontWeight(700),
fontFamily = sans
)
}
Configuring Resolve Aliases
The key setting that determines how {token.path} references are handled:
CSS
resolveAliases: false (default for CSS) — aliases are preserved as var():
--color-text-primary: var(--color-brand-primary);
--typography-heading-xl: var(--font-weight-bold) var(--font-size-3xl)/var(--line-height-relaxed) var(--font-family-sans);
resolveAliases: true — all values fully resolved:
--color-text-primary: #0066FF;
--typography-heading-xl: 700 30px/24px Inter;
Recommendation for CSS: use
resolveAliases: false. This preserves cascading — when a base variable changes, all dependents update automatically.
SwiftUI / Kotlin
resolveAliases: true (default) — final literals:
static let primary = Color(red: 0, green: 0.4, blue: 1, opacity: 1)
resolveAliases: false — references to constants + @ref comments:
/// @ref {color.brand.primary}
static let primary = colorBrandPrimary // ← direct reference to constant
Effects handling (CSS)
For CSS, the splitEffects setting controls how mixed effects (shadow + blur + backdrop-blur in one token) are handled.
splitEffects: true (default) — split into separate variables:
/* box-shadow */
--effects-card: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
/* filter */
--effects-card-blur: blur(2px);
/* backdrop-filter */
--effects-card-backdrop-blur: blur(20px);
Usage:
.card {
box-shadow: var(--effects-card);
filter: var(--effects-card-blur);
backdrop-filter: var(--effects-card-backdrop-blur);
}
splitEffects: false — shadow part only:
--effects-card: 0 2px 8px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
Token descriptions in code
With showDescriptions: true (default), the $description field from JSON is added as a comment:
CSS:
/* Primary brand color */
--color-primary: #0066FF;
SwiftUI:
/// Primary brand color
static let colorPrimary = Color(red: 0, green: 0.4, blue: 1, opacity: 1)
Kotlin:
/** Primary brand color */
val ColorPrimary = Color(0xFF0066FF)
Code Syntax from $extensions
If a token contains $extensions.figma.codeSyntax, the transformer uses the specified names instead of auto-generating them:
{
"color": {
"primary": {
"$type": "color",
"$value": "#0066FF",
"$extensions": {
"figma.codeSyntax": {
"Web": "var(--color-primary)",
"iOS": "Color.primary",
"Android": "@color/primary"
}
}
}
}
}
When generating CSS, the variable name is taken from Web; for SwiftUI from iOS; for Kotlin from Android.
More details in Scopes & Code Syntax.
Mathematical expressions
Tokens support math expressions that are evaluated during transformation:
{
"spacing": {
"base": { "$type": "spacing", "$value": "4px" },
"sm": { "$type": "spacing", "$value": "{spacing.base} * 2" },
"md": { "$type": "spacing", "$value": "{spacing.base} * 4" },
"lg": { "$type": "spacing", "$value": "{spacing.base} * 6" }
}
}
CSS result:
:root {
--spacing-base: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
}
Supported operations: +, -, *, /, round().
Variable prefix
The prefix setting adds a prefix to variable names:
| Platform | prefix | Example |
|---|---|---|
| CSS | "" | --color-primary |
| CSS | "ds" | --ds-color-primary |
| SwiftUI | "" | Color.colorPrimary, enum Spacing |
| SwiftUI | "DS" | Color.dsColorPrimary, enum DSSpacing |
| Kotlin | "" | DSColors.ColorPrimary |
| Kotlin | "App" | AppColors.ColorPrimary |
Vue 3 — generation from Composition
A separate transformer converts Composition tokens into ready-to-use Vue 3 Single File Components (.vue).
Running via CLI
npx tsx src/cli.ts --composition=path/to/button.json --output=./components
What gets generated
A complete .vue file with three sections:
<template>— HTML structure fromstructure(FRAME →<div>, TEXT →<span>, INSTANCE → component, BOOLEAN →v-if)<script setup lang="ts">— types, props, defaults, computed classes for variants<style module>— CSS fromstylesandadapters, Figma → CSS mapping, pseudo-classes for states
Example output
<template>
<button
:class="[$style['btn'], variantClass, sizeClass,
{ [$style['btn--disabled']]: disabled }]"
:disabled="disabled || undefined"
>
<WIcon v-if="isIconLeft" :class="$style['btn-icon-left']" :name="iconLeft" />
<span :class="$style['btn-label']">{{ label }}</span>
</button>
</template>
<script setup lang="ts">
import { computed, useCssModule } from 'vue'
type Props = {
variant?: 'accent' | 'secondary' | 'tertiary'
size?: 'sm' | 'md' | 'lg'
label?: string
isIconLeft?: boolean
iconLeft?: string
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
variant: 'accent',
size: 'sm',
label: 'Button',
isIconLeft: false,
iconLeft: '',
disabled: false,
})
const $style = useCssModule()
const variantClass = computed(() => {
const map: Record<NonNullable<Props['variant']>, string> = {
'accent': $style['btn--accent'],
'secondary': $style['btn--secondary'],
'tertiary': $style['btn--tertiary'],
}
return map[props.variant ?? 'accent']
})
</script>
<style module>
.btn {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 8px;
background-color: var(--accent-medium);
}
.btn--accent { background-color: var(--info-medium); }
.btn:hover { opacity: 0.88; }
.btn--disabled, .btn:disabled { opacity: 0.4; pointer-events: none; }
</style>
Figma → CSS mapping
| Figma property | CSS |
|---|---|
layoutMode: "HORIZONTAL" | display: flex |
layoutMode: "VERTICAL" | display: flex; flex-direction: column |
primaryAxisAlignItems | justify-content |
counterAxisAlignItems | align-items |
layoutSizingHorizontal: "HUG" | width: fit-content |
layoutSizingHorizontal: "FILL" | flex: 1 |
itemSpacing | gap |
padding* | padding (shorthand) |
cornerRadius | border-radius |
fills: ["{token}"] | background-color: var(--token) / color: var(--token) |
strokeWeight + strokes | border |
opacity | opacity |
clipsContent | overflow: hidden |
fontSize | font-size |
fontFamily | font-family |
fontWeight | font-weight |
textAlignHorizontal | text-align |
More about Composition format in Composition.
Transformer utility (CLI)
For CI/CD integration or local builds, use the standalone Transformer utility.
Installation and running
cd Transformer
npm install
# Run with config
npx tsx src/cli.ts --config=sxl-transform.config.json
# Generate default config
npx tsx src/cli.ts --init
# Vue 3 from Composition
npx tsx src/cli.ts --composition=path/to/component.json --output=./output
Transformer configuration
{
"source": {
"tokenDir": "./tokens",
"configFile": "config.json",
"include": ["core/*.json", "themes/*.json"],
"exclude": ["config.json", "**/diff-id*.json"]
},
"platforms": {
"css": {
"outputDir": "./dist/css",
"prefix": "ds",
"resolveAliases": false,
"splitEffects": true,
"showDescriptions": true,
"fileMapping": [
{
"sources": ["core/*.json"],
"output": "primitives.css",
"filter": {
"types": ["color", "dimension"],
"paths": ["color."],
"excludePaths": ["color.internal."]
}
},
{
"sources": ["themes/light.json"],
"output": "themes/light.css"
}
]
},
"swiftui": {
"outputDir": "./dist/ios",
"prefix": "DS",
"resolveAliases": true,
"showDescriptions": true,
"fileMapping": [
{
"sources": ["core/*.json"],
"output": "DSTokens.swift"
}
]
},
"kotlin": {
"outputDir": "./dist/android",
"prefix": "DS",
"resolveAliases": true,
"showDescriptions": true,
"fileMapping": [
{
"sources": ["core/*.json"],
"output": "DSTokens.kt"
}
]
}
},
"settings": {
"remBase": 16,
"verbose": false
}
}
Transformer config field descriptions
| Section | Field | Description |
|---|---|---|
source | tokenDir | Path to the folder with JSON tokens |
source | configFile | Path to the plugin's config.json (for grouping) |
source | include | Glob patterns for files to include |
source | exclude | Glob patterns for files to exclude |
platforms.* | outputDir | Output directory |
platforms.* | prefix | Variable name prefix |
platforms.* | resolveAliases | Resolve aliases to final values |
platforms.* | splitEffects | Split mixed effects (CSS only) |
platforms.* | showDescriptions | Include $description as comments |
platforms.* | fileMapping | Rules for combining tokens into output files |
settings | remBase | Base value for rem → px |
settings | verbose | Verbose logging |
fileMapping — combining files
Lets you combine tokens from multiple JSON files into a single output file:
{
"sources": ["core/palette.json", "themes/light.json"],
"output": "core.css",
"filter": {
"types": ["color", "dimension"],
"paths": ["color.primary"],
"excludePaths": ["color.internal"]
}
}
| Field | Description |
|---|---|
sources | Array of paths (supports * wildcards) |
output | Output file name (relative to outputDir) |
filter.types | Filter by token type |
filter.paths | Include only paths starting with specified prefixes |
filter.excludePaths | Exclude paths starting with specified prefixes |
Tips
- CSS →
resolveAliases: false— preserve cascading throughvar(). - Swift/Kotlin →
resolveAliases: true— mobile platforms don't support native variable references. - Use
fileMapping— group tokens by purpose (primitives.css, themes/light.css, components.css). $descriptionin JSON — descriptions become code comments, improving readability for developers.- Code Syntax in
$extensions— if auto-naming doesn't fit, set names manually viafigma.codeSyntax. - CI/CD — add
npx tsx src/cli.ts --config=...to your build pipeline to keep token code always up to date.
Related sections
- Tokens Overview — general structure
- Token Types — all supported types and their values
- Scopes & Code Syntax — code settings for Dev Mode
- Export Variables & Styles — exporting to Figma
- Composition — component generation (Vue 3 input)