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 were far from fully formed, at that time existing more or less as a concept. It would have been premature to adopt it.
So 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 processes that are standard engineering best practices. But that approach also requires a degree of complexity that most small startups don’t want to take on. Using Figma remains 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.
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 8 could be spacing, a font size, a z-index, or some custom value. 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 still far more limited, but less so than they were. Alongside Figma Motion, the August 2026 plugin API update added TIMING and EASING, bringing the list to six.
| Figma type | Resolves to |
|---|---|
color | A hex, RGBA, or HSLA color value, optionally composed with a separate opacity |
number | A unitless numeric value, uses FLOAT in Figma code |
string | A text string |
boolean | true or false |
timing | A duration, uses TIMING in Figma code |
easing | An easing curve or spring animation, uses EASING in Figma code |
In the plugin API that’s:
type VariableResolvedDataType = 'BOOLEAN' | 'COLOR' | 'EASING' | 'FLOAT' | 'STRING' | 'TIMING' 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:
ALL_FILLS, ALL_SCOPES, COLOR_OPACITY, CORNER_RADIUS, EFFECT_COLOR, EFFECT_FLOAT, FONT_FAMILY, FONT_SIZE, FONT_STYLE, FONT_WEIGHT, FRAME_FILL, GAP, LETTER_SPACING, LINE_HEIGHT, OPACITY, PARAGRAPH_INDENT, PARAGRAPH_SPACING, SHAPE_FILL, STROKE_COLOR, STROKE_FLOAT, TEXT_CONTENT, TEXT_FILL, and WIDTH_HEIGHT.
Scopes only apply to color, number, and string variables. The two motion types have nothing to scope — a TIMING variable is applied to an animation’s duration or delay and an EASING variable to a preset or keyframe, both in Figma Motion — so their scopes array can be ignored entirely.
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 introducing unwanted values in the output. Any changes to variables and their output to code should be reviewed carefully.
- Some values you want in code still have no Figma type — dash arrays, line caps, and gradient stops among them. For those, create a
STRINGvariable and intentionally leave thescopeempty to avoid surfacing it in the Figma UI. This also tells the converting script to let the value pass through unchanged, unlikedimensiontokens that appendpx. Before the motion types existed this was also the workaround for durations and easing curves; those should now be migrated toTIMINGandEASING. - Timing values are entered in the Figma UI in milliseconds but the plugin API reports them in seconds, so a 150ms duration comes back as
0.15. Confirm the unit against your own export before you write the multiplier into a conversion script.
Mapping primitive types
color
A color value in sRGB space.
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"]
} Example W3C token
{
"fg-base": {
"$type": "color",
"$value": "#0066cc"
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
COLOR | mode | FRAME_FILL, SHAPE_FILL, TEXT_FILL, STROKE_COLOR, EFFECT_COLOR |
A color can also be composed from a color plus a separate opacity, where at least one half is an alias to another variable. Those arrive as { "color": { "type": "VARIABLE_ALIAS", "id": "…" }, "opacity": 0.5 } and are scoped with COLOR_OPACITY, so resolve both halves before flattening them into a single token value.
dimension
A length value with a CSS unit — typically px, rem, or em.
Example Figma JSON
{
"name": "spacing/md",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 16
},
"scopes": ["WIDTH_HEIGHT", "CORNER_RADIUS", "GAP", "STROKE_FLOAT"]
} Example W3C token
{
"spacing-md": {
"$type": "dimension",
"$value": "16px"
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
FLOAT | mode | WIDTH_HEIGHT, CORNER_RADIUS, GAP, STROKE_FLOAT |
font-family
The name of a typeface or a comma-separated font stack.
Example Figma JSON
{
"name": "font/family/sans",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "Inter"
},
"scopes": ["FONT_FAMILY"]
} Example W3C token
{
"font-sans": {
"$type": "font-family",
"$value": "Inter"
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
STRING | mode | FONT_FAMILY |
font-weight
A numeric font weight from 100 to 900.
Example Figma JSON
{
"name": "font/weight/semibold",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 600
},
"scopes": ["FONT_WEIGHT"]
} Example W3C token
{
"font-weight-semibold": {
"$type": "font-weight",
"$value": 600
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
FLOAT | mode | FONT_WEIGHT |
font-size
A type size value with a unit. Figma stores the raw number; units are implied by context.
Example Figma JSON
{
"name": "text/size/lg",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 18
},
"scopes": ["FONT_SIZE"]
} Example W3C token
{
"text-lg": {
"$type": "font-size",
"$value": "18px"
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
FLOAT | mode | FONT_SIZE |
line-height
The vertical spacing between lines of text, expressed as a unitless multiplier or a length.
Example Figma JSON
{
"name": "text/leading/normal",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 1.5
},
"scopes": ["LINE_HEIGHT"]
} Example W3C token
{
"leading-normal": {
"$type": "line-height",
"$value": 1.5
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
FLOAT | mode | 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 Figma JSON
{
"name": "text/tracking/tight",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": -2
},
"scopes": ["LETTER_SPACING"]
} Example W3C token
{
"tracking-tight": {
"$type": "letter-spacing",
"$value": "-0.02em"
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
FLOAT | mode | 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 Figma JSON
{
"name": "opacity/disabled",
"resolvedType": "FLOAT", // "number"
"valuesByMode": {
"mode": 0.4
},
"scopes": ["OPACITY"]
} Example W3C token
{
"opacity-disabled": {
"$type": "number",
"$value": 0.4
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
FLOAT | mode | OPACITY |
string
An arbitrary text value that isn’t a color, font family, or numeric.
Example Figma JSON
{
"name": "brand/name",
"resolvedType": "STRING",
"valuesByMode": {
"mode": "Genoni Studio"
},
"scopes": ["TEXT_CONTENT"]
} Example W3C token
{
"brand-name": {
"$type": "string",
"$value": "Genoni Studio"
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
STRING | mode | TEXT_CONTENT, FONT_FAMILY |
Strings with empty scopes can still be used to pass through values Figma has no type for, like a stroke dash array.
boolean
A true/false flag, used in Figma primarily to toggle layer visibility per mode.
Example Figma JSON
{
"name": "show/divider",
"resolvedType": "BOOLEAN",
"valuesByMode": {
"mode": true
},
"scopes": []
} Example W3C token
{
"show-divider": {
"$type": "boolean",
"$value": true
}
} Convert with:
| resolvedType | valuesByMode |
|---|---|
BOOLEAN | mode |
duration
A time value for animation and transition timing. This is now a first-class TIMING variable that Figma Motion can bind to an animation’s duration or delay — no more parking the value in a scope-less string.
Example Figma JSON
{
"name": "duration/fast",
"resolvedType": "TIMING",
"valuesByMode": {
"mode": 0.15 // seconds; entered as 150ms in the Figma UI
},
"scopes": []
} Example W3C token
{
"duration-fast": {
"$type": "duration",
"$value": "150ms"
}
} Convert with:
| resolvedType | valuesByMode |
|---|---|
TIMING | mode |
The W3C duration type requires an explicit ms or s unit, so the conversion is a multiply-by-1000 and a suffix rather than a pass-through.
cubic-bezier
A bezier curve defined by four control points [x1, y1, x2, y2] describing an easing function. An EASING variable holds a MotionEasing object rather than a bare array, and only some of its shapes have a cubic-bezier equivalent.
Example Figma JSON
{
"name": "easing/out",
"resolvedType": "EASING",
"valuesByMode": {
"mode": {
"type": "CUSTOM_CUBIC_BEZIER",
"easingFunctionCubicBezier": { "x1": 0, "y1": 0, "x2": 0.58, "y2": 1 }
}
},
"scopes": []
} Example W3C token
{
"easing-out": {
"$type": "cubic-bezier",
"$value": [0, 0, 0.58, 1]
}
} Convert with:
| resolvedType | valuesByMode.mode |
|---|---|
EASING | type and easingFunctionCubicBezier |
The MotionEasing type covers more than custom curves, and the rest need a decision from you:
- The curve presets —
LINEAR,EASE_IN,EASE_OUT,EASE_IN_AND_OUT, and the three_BACKvariants — carry no control points. Keep a lookup table of the equivalent curves in your script and emit those ascubic-beziertokens. - The spring types —
CUSTOM_SPRINGplus theGENTLE,QUICK,BOUNCY, andSLOWpresets — are physics, not curves, and have nocubic-bezierrepresentation. A custom spring arrives aseasingFunctionSpring: { "bounce": 0.3 }, a normalized0to1scalar;figma.motion.physicalSpringToNormalized({ mass, stiffness, damping })converts the physical parameters into it. Emit these asstringtokens for the platforms that can use them, or leave them in Figma. HOLDhas no CSS equivalent at all.
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 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"]
} Example W3C token
{
"border-default": {
"$type": "border",
"$value": {
"color": "#e2e8f0",
"width": "1px",
"style": "solid"
}
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
COLOR | mode | STROKE_COLOR |
FLOAT | mode | 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 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"]
} Example W3C token
{
"text-heading-xl": {
"$type": "typography",
"$value": {
"fontFamily": "Inter",
"fontSize": "48px",
"fontWeight": 700,
"lineHeight": 1.1,
"letterSpacing": "-0.03em"
}
}
} Convert with:
| resolvedType | valuesByMode | scopes |
|---|---|---|
STRING | mode | FONT_FAMILY |
FLOAT | mode | FONT_SIZE |
FLOAT | mode | FONT_WEIGHT |
FLOAT | mode | LINE_HEIGHT |
FLOAT | mode | 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 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"]
} Example W3C token
{
"shadow-md": {
"$type": "shadow",
"$value": {
"offsetX": "0",
"offsetY": "4px",
"blur": "8px",
"spread": "0",
"color": "#00000026"
}
}
} 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.
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,
}
],
}
]
...
} Example W3C token
{
"gradient-base": {
"$type": "gradient",
"$value": [
{ "color": "#0066cc", "position": 0 },
{ "color": "#00aaff", "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. With TIMING and EASING in place, every part of this token now has a native Figma variable — decompose it into three.
Example Figma JSON
{
"name": "transition/base/duration",
"resolvedType": "TIMING",
"valuesByMode": {
"mode": 0.2 // seconds
},
"scopes": []
} {
"name": "transition/base/timing-function",
"resolvedType": "EASING",
"valuesByMode": {
"mode": {
"type": "CUSTOM_CUBIC_BEZIER",
"easingFunctionCubicBezier": { "x1": 0.4, "y1": 0, "x2": 0.2, "y2": 1 }
}
},
"scopes": []
} {
"name": "transition/base/delay",
"resolvedType": "TIMING",
"valuesByMode": {
"mode": 0
},
"scopes": []
} Example W3C token
{
"transition-base": {
"$type": "transition",
"$value": {
"duration": "200ms",
"timingFunction": [0.4, 0, 0.2, 1],
"delay": "0ms"
}
}
} Convert with:
| resolvedType | valuesByMode |
|---|---|
TIMING | mode |
EASING | type and easingFunctionCubicBezier |
A spring easing breaks this mapping — the transition type’s timingFunction only accepts a cubic bezier or a keyword, so a CUSTOM_SPRING has to be carried outside the composite token.
stroke-style
Describes the dash pattern and line cap of a stroke. Figma has no variable type for dash arrays or line caps — this token is code-only.
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": []
} Example W3C token
{
"stroke-dashed": {
"$type": "stroke-style",
"$value": {
"dashArray": ["4px", "4px"],
"lineCap": "round"
}
}
} Convert with:
| name | resolvedType |
|---|---|
| “stroke” & “dash-array” | STRING |
| “stroke” & “line-cap” | STRING |
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. Figma adding timing and easing variables is a good step in that direction, as two of the mappings in this post went from naming conventions and empty scopes to actual types. But until the rest of the gaps close, the process is going to be messy with a fair amount of custom code, manual mapping, and a few hacks.
Originally posted April 23, 2026.