At Figma’s Config conference in June of 2023, many of us designer/developer folks had our fingers crossed that support for design tokens would be a first-class feature of Figma. Instead what we got was their “answer to tokens” in the form of Figma Variables. Though initially disappointed, I realized that despite an emerging specification, design tokens was far from fully formed, at that time existing more or less as a concept. It would have been premature to adopt it.
So the question of where to locate the “source of truth” for design tokens was (and is) still an open question. Using a git repository with tools like Tokens Studio and Style Dictionary provides version control and package management options that are standard engineering best practices. But that approach also requires a degree of complexity and management that many small startups don’t want to take on, so using Figma is an attractive, if incomplete, option.
Though there are many Figma plugins that will export variables and styles directly to CSS, JavaScript, Swift, and other code formats, some teams prefer to have more granular control over that conversion. As someone who regularly deals with getting Figma Variables into code — either first to the Design Token spec format or directly to a code implementation — I thought I’d share the Figma settings I use to make this process more predictable. To create the simplified Figma code examples below I used the “Export/Import Variables” plugin, which exports the raw Figma API output of the variables.
Design token vs Figma variable types
Design token types
The W3C Design Tokens Community Group specification defines a standard JSON format for sharing design tokens across tools, platforms, and teams. Each token carries a $type field that tells consuming tools — build systems, style processors, code generators — how to interpret and transform its value. Without types, a value like 16 could be spacing, a font size, an opacity, or a z-index. The type removes that ambiguity and is what makes design tokens portable.
The spec defines 18 types split across two categories. Primitive types map directly to a single scalar value:
| Property | type | Spec link |
|---|---|---|
color | color | Spec ↗ |
dimension | dimension | Spec ↗ |
font-family | fontFamily | Spec ↗ |
font-weight | fontWeight | Spec ↗ |
font-size | fontSize | Spec ↗ |
line-height | lineHeight | Spec ↗ |
letter-spacing | letterSpacing | Spec ↗ |
number | number | Spec ↗ |
string | string | Spec ↗ |
boolean | boolean | Spec ↗ |
duration | duration | Spec ↗ |
cubic-bezier | cubicBezier | Spec ↗ |
And Composite types, which bundle multiple values into a structured token object:
| Concept | type | Spec link |
|---|---|---|
border | border | Spec ↗ |
typography | typography | Spec ↗ |
shadow | shadow | Spec ↗ |
gradient | gradient | Spec ↗ |
transition | transition | Spec ↗ |
stroke-style | strokeStyle | Spec ↗ |
Figma variable types
Figma’s variable types are far more limited.
| Figma type | Resolves to |
|---|---|
color | A hex, RGBA, or HSLA color value |
number | A unitless numeric value, uses FLOAT in Figma code. |
string | A text string |
boolean | true or false |
Figma variable scopes
To map them to a design token type obviously requires more information. To do that we can mostly rely on Figma’s variable scoping. This feature’s primary purpose is to filter the options displayed in the Figma UI only to those variables that are available for a selected node. For example, this can prevent a designer from applying a color to text that was intended only for use as a background fill.
Figma’s available variable scope options are:
| Scope |
|---|
ALL_SCOPES |
TEXT_CONTENT |
CORNER_RADIUS |
WIDTH_HEIGHT |
GAP |
ALL_FILLS |
FRAME_FILL |
SHAPE_FILL |
TEXT_FILL |
STROKE_COLOR |
EFFECT_COLOR |
STROKE_FLOAT |
EFFECT_FLOAT |
OPACITY |
FONT_FAMILY |
FONT_STYLE |
FONT_WEIGHT |
FONT_SIZE |
LINE_HEIGHT |
LETTER_SPACING |
PARAGRAPH_SPACING |
PARAGRAPH_INDENT |
By combining the Figma variable’s resolvedType, one or more of the possible entries in its scopes array, and occasionally the variable name, we can reliably convert Figma variables to a token.
Important notes & warnings
- Any designer can change Figma variables and scopes without a review, breaking conversion scripts, or worse, introducing unwanted values in the output. Any changes to variables and their output to code should be reviewed carefully.
- A number of Figma variables you’ll create, like duration or cubic-bezier values, are there to provide the design team with control over the value used in code, and are not meant to be used by Figma directly. For these values we intentionally leave the
scopeempty to avoid surfacing it in the Figma UI. This also tells the converting script to let the value pass through unchanged, unlikedimensiontokens that appendpx.
Mapping primitive types
color
A color value in sRGB space.
Example W3C token
{
"fg-base": {
"$type": "color",
"$value": "#0066cc"
}
} Example Figma JSON
{
"name": "fg/base",
"resolvedType": "COLOR",
"valuesByMode": {
"mode": { "r": 0, "g": 0.4, "b": 0.8, "a": 1 }
},
"scopes": ["FRAME_FILL", "SHAPE_FILL", "TEXT_FILL", "STROKE_COLOR", "EFFECT_COLOR"]
} Convert with:
| resolvedType | scopes |
|---|---|
COLOR | FRAME_FILL, SHAPE_FILL, TEXT_FILL, STROKE_COLOR, EFFECT_COLOR |
dimension
A length value with a CSS unit — typically px, rem, or em.
Example W3C token
{
"spacing-md": {
"$type": "dimension",
"$value": "16px"
}
} Example Figma JSON
{
"name": "spacing/md",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 16
},
"scopes": ["WIDTH_HEIGHT", "CORNER_RADIUS", "GAP", "STROKE_FLOAT"]
} Convert with:
| resolvedType | scopes |
|---|---|
FLOAT | WIDTH_HEIGHT, CORNER_RADIUS, GAP, STROKE_FLOAT |
font-family
The name of a typeface or a comma-separated font stack.
Example W3C token
{
"font-sans": {
"$type": "font-family",
"$value": "Inter"
}
} Example Figma JSON
{
"name": "font/family/sans",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "Inter"
},
"scopes": ["FONT_FAMILY"]
} Convert with:
| resolvedType | scopes |
|---|---|
STRING | FONT_FAMILY |
font-weight
A numeric font weight from 100 to 900.
Example W3C token
{
"font-weight-semibold": {
"$type": "font-weight",
"$value": 600
}
} Example Figma JSON
{
"name": "font/weight/semibold",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 600
},
"scopes": ["FONT_WEIGHT"]
} Convert with:
| resolvedType | scopes |
|---|---|
FLOAT | FONT_WEIGHT |
font-size
A type size value with a unit. Figma stores the raw number; units are implied by context.
Example W3C token
{
"text-lg": {
"$type": "font-size",
"$value": "18px"
}
} Example Figma JSON
{
"name": "text/size/lg",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 18
},
"scopes": ["FONT_SIZE"]
} Convert with:
| resolvedType | scopes |
|---|---|
FLOAT | FONT_SIZE |
line-height
The vertical spacing between lines of text, expressed as a unitless multiplier or a length.
Example W3C token
{
"leading-normal": {
"$type": "line-height",
"$value": 1.5
}
} Example Figma JSON
{
"name": "text/leading/normal",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 1.5
},
"scopes": ["LINE_HEIGHT"]
} Convert with:
| resolvedType | scopes |
|---|---|
FLOAT | LINE_HEIGHT |
letter-spacing
The horizontal spacing between characters, typically in em units. Figma stores this as a percentage of font size, so -0.02em is stored as -2. A transform step handles the unit conversion.
Example W3C token
{
"tracking-tight": {
"$type": "letter-spacing",
"$value": "-0.02em"
}
} Example Figma JSON
{
"name": "text/tracking/tight",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": -2
},
"scopes": ["LETTER_SPACING"]
} Convert with:
| resolvedType | scopes |
|---|---|
FLOAT | LETTER_SPACING |
Note that letter-spacing Figma variables must be defined as a number. A STRING variable with a percentage can’t be applied as a letter-spacing variable.
number
A raw unitless number — opacity, z-index, scale factors, and similar values that don’t carry a CSS unit.
Example W3C token
{
"opacity-disabled": {
"$type": "number",
"$value": 0.4
}
} Example Figma JSON
{
"name": "opacity/disabled",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 0.4
},
"scopes": ["OPACITY"]
} Convert with:
| resolvedType | scopes |
|---|---|
FLOAT | OPACITY |
string
An arbitrary text value that isn’t a color, font family, or numeric.
Example W3C token
{
"brand-name": {
"$type": "string",
"$value": "Genoni Studio"
}
} Example Figma JSON
{
"name": "brand/name",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "Genoni Studio"
},
"scopes": ["TEXT_CONTENT"]
} Convert with:
| resolvedType | scopes |
|---|---|
STRING | TEXT_CONTENT, FONT_FAMILY |
Strings with empty scopes can be used to pass through values unchanged, like a duration value of “300ms”.
boolean
A true/false flag, used in Figma primarily to toggle layer visibility per mode.
Example W3C token
{
"show-divider": {
"$type": "boolean",
"$value": true
}
} Example Figma JSON
{
"name": "show/divider",
"resolvedType": "BOOLEAN",
"valuesByMode": {
"mode": true
},
"scopes": []
} Convert with:
| resolvedType | valuesByMode |
|---|---|
BOOLEAN | mode |
duration
A time value for animation and transition timing. Figma has no motion scope — a FLOAT can store the raw number but won’t bind to any animation property.
Example W3C token
{
"duration-fast": {
"$type": "duration",
"$value": "150ms"
}
} Example Figma JSON (no motion scope)
{
"name": "duration/fast",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "150ms"
},
"scopes": []
} Convert with:
| name contains | resolvedType | scopes |
|---|---|---|
| “duration” | STRING | [empty] |
cubic-bezier
A bezier curve defined by four control points [x1, y1, x2, y2] describing an easing function. Figma has no motion scope and no way to bind this to animation properties.
Example W3C token
{
"easing-out": {
"$type": "cubic-bezier",
"$value": [0, 0, 0.58, 1]
}
} Example Figma JSON (no motion scope)
{
"name": "easing/out",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "0, 0, 0.58, 1"
},
"scopes": []
} Convert with:
| name contains | resolvedType | scopes |
|---|---|---|
| “easing” | STRING | [empty] |
Mapping composite types
border
A shorthand combining color, width, and style. Split into two Figma variables; the style value is expressed separately via stroke-style.
Example W3C token
{
"border-default": {
"$type": "border",
"$value": {
"color": "#e2e8f0",
"width": "1px",
"style": "solid"
}
}
} Example Figma JSON
{
"name": "border/color/base",
"resolvedType": "COLOR",
"valuesByMode": {
"mode": { "r": 0.886, "g": 0.91, "b": 0.941, "a": 1 }
},
"scopes": ["STROKE_COLOR"]
} {
"name": "border/width/base",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 1
},
"scopes": ["STROKE_FLOAT"]
} Convert with:
| resolvedType | scopes |
|---|---|
COLOR | STROKE_COLOR |
FLOAT | STROKE_FLOAT |
typography
A complete text style bundling family, size, weight, line height, and letter spacing. Decompose into five separate Figma variables; Text Styles can then reference them.
Example W3C token
{
"text-heading-xl": {
"$type": "typography",
"$value": {
"fontFamily": "Inter",
"fontSize": "48px",
"fontWeight": 700,
"lineHeight": 1.1,
"letterSpacing": "-0.03em"
}
}
} Example Figma JSON
{
"name": "heading/xl/family",
"resolvedType": "STRING",
"valuesByMode": { "mode": "Inter" },
"scopes": ["FONT_FAMILY"]
} {
"name": "heading/xl/size",
"resolvedType": "FLOAT", // "number"
"valuesByMode": { "mode": 48 },
"scopes": ["FONT_SIZE"]
} {
"name": "heading/xl/weight",
"resolvedType": "FLOAT", // "number"
"valuesByMode": { "mode": 700 },
"scopes": ["FONT_WEIGHT"]
} {
"name": "heading/xl/line-height",
"resolvedType": "FLOAT", // "number"
"valuesByMode": { "mode": 1.1 },
"scopes": ["LINE_HEIGHT"]
} {
"name": "heading/xl/letter-spacing",
"resolvedType": "FLOAT", // "number"
"valuesByMode": { "mode": -3 },
"scopes": ["LETTER_SPACING"]
} Convert with:
| resolvedType | scopes |
|---|---|
STRING | FONT_FAMILY |
FLOAT | FONT_SIZE |
FLOAT | FONT_WEIGHT |
FLOAT | LINE_HEIGHT |
FLOAT | LETTER_SPACING |
shadow
A drop or inner shadow defined by offset, blur, spread, and color. The color feeds a Figma Effect; the numeric values must be mapped manually or via a sync tool.
Example W3C token
{
"shadow-md": {
"$type": "shadow",
"$value": {
"offsetX": "0",
"offsetY": "4px",
"blur": "8px",
"spread": "0",
"color": "#00000026"
}
}
} Example Figma JSON
{
"name": "shadow/md/offset-x",
"resolvedType": "FLOAT", // "number"
"valuesByMode": { "mode": 0 },
"scopes": []
} {
"name": "shadow/md/offset-y",
"resolvedType": "FLOAT", // "number"
"valuesByMode": { "mode": 4 },
"scopes": []
} {
"name": "shadow/md/blur",
"resolvedType": "FLOAT", // "number"
"valuesByMode": { "mode": 8 },
"scopes": []
} {
"name": "shadow/md/spread",
"resolvedType": "FLOAT", // "number"
"valuesByMode": { "mode": 0 },
"scopes": []
} {
"name": "shadow/md/color",
"resolvedType": "COLOR",
"valuesByMode": {
"mode": { "r": 0, "g": 0, "b": 0, "a": 0.15 }
},
"scopes": ["EFFECT_COLOR"]
} Convert with:
| name | resolvedType | scopes |
|---|---|---|
| “shadow” & “offset-x” | FLOAT | — |
| “shadow” & “offset-y” | FLOAT | — |
| “shadow” & “blur” | FLOAT | — |
| “shadow” & “spread” | FLOAT | — |
| “shadow” | COLOR | EFFECT_COLOR |
Note you could use a single STRING to define most of this — "0, 4, 8, 0" — but it would be incomplete because the shadow takes in a color; you’d have to define that separately. Unlike with easing, I prefer to create a variable for each part.
gradient
A color stop list defining a gradient fill. Figma has no gradient variable type. Note that if transparency is needed, raw hex codes with transparency values must be used to specify the stops.
{
"gradient-base": {
"$type": "gradient",
"$value": [
{ "color": "#0066cc", "position": 0 },
{ "color": "#00aaff", "position": 1 }
]
}
} Simplified example Figma JSON of a gradient color style.
{
...
"name": "gradient/base",
"type": "PAINT",
"paints": [
{
"type": "GRADIENT_LINEAR",
"gradientStops": [
{
"color": {
"r": 1,
"g": 1,
"b": 1,
"a": 1
},
"position": 0,
},
{
"color": {
"r": 0.6000000238418579,
"g": 0.6000000238418579,
"b": 0.6000000238418579,
"a": 1
},
"position": 1,
}
],
}
]
...
} In this case you can look for PAINT and GRADIENT_LINEAR and use the gradientStops to generate the token. However, you may have other properties to convert like gradientTransform and opacity values.
transition
Combines duration, easing, and delay into a single animation transition token. Figma has no variable types for motion — this token has no Figma equivalent.
Example W3C token
{
"transition-base": {
"$type": "transition",
"$value": {
"duration": "200ms",
"timingFunction": [0.4, 0, 0.2, 1],
"delay": "0ms"
}
}
} Example Figma JSON
{
"name": "transition/base/duration",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "200ms"
},
"scopes": []
} {
"name": "transition/base/timing-function",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "0.4, 0, 0.2, 1"
},
"scopes": []
} {
"name": "transition/base/delay",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "0ms"
},
"scopes": []
} stroke-style
Describes the dash pattern and line cap of a stroke. Figma has no variable scope for dash arrays or line caps — this token is code-only.
Example W3C token
{
"stroke-dashed": {
"$type": "stroke-style",
"$value": {
"dashArray": ["4px", "4px"],
"lineCap": "round"
}
}
} Example Figma JSON
{
"name": "stroke/dashed/dash-array",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "4px, 4px"
},
"scopes": []
} {
"name": "stroke/dashed/line-cap",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "round"
},
"scopes": []
} Summary
The promise of design tokens is that, eventually, we’ll have a complete design token specification that platforms can adopt, not unlike the way applications support the SVG specification. But until then the process is going to be messy with a lot of custom code, manual mapping, and a fair number of hacks.