Master Figma's variable system — create and organize color, spacing, and typography tokens, use modes for light/dark themes, and export the complete token set to CSS custom properties using Tokens Studio and Style Dictionary.
Design tokens are the named values that replace hardcoded colors, spacing, and typography values throughout a codebase. Instead of writing color: #2563eb in a button and color: #2563eb in a badge, you write color: var(--color-brand-primary) in both — and the actual hue lives in one place: the token definition. When the brand color changes, one update propagates everywhere.
Figma's variable system (introduced in 2023) brings this concept natively into the design tool. Variables defined in Figma can be exported directly to CSS custom properties, eliminating the manual copy-paste that causes drift between design and code. This lesson builds the complete token pipeline from Figma Variables to production CSS.
A well-organized token system has two layers:
Primitive tokens: raw values with no semantic meaning. They define the full palette of available values.
Semantic tokens: named by purpose, referencing primitives. They communicate what a value is FOR, not what it is.
This two-layer architecture means:
Figma Variables panel organization:
CSS equivalent of the two-layer system:
1/* Layer 1: Primitive tokens — raw values */
2:root {
3 --primitive-blue-500: hsl(220, 90%, 56%);
4 --primitive-blue-600: hsl(220, 90%, 46%);
5 --primitive-gray-50: hsl(220, 20%, 97%);
6 --primitive-gray-900: hsl(220, 30%, 10%);
7 --primitive-space-4: 1rem;
8 --primitive-space-6: 1.5rem;
9 --primitive-radius-md: 0.5rem;
10}
11
12/* Layer 2: Semantic tokens — reference primitives */
13:root {
14 /* Action colors */
15 --color-action-primary: var(--primitive-blue-500);
16 --color-action-primary-hover: var(--primitive-blue-600);
17 /* Surface colors */
18 --color-surface-default: white;
19 --color-surface-alt: var(--primitive-gray-50);
20 /* Text colors */
21 --color-text-primary: var(--primitive-gray-900);
22 /* Spacing */
23 --space-component-md: var(--primitive-space-4);
24 --space-layout-section: var(--primitive-space-6);
25 /* Radius */
26 --radius-component-md: var(--primitive-radius-md);
27}
28
29@media (prefers-color-scheme: dark) {
30 :root {
31 /* Semantic tokens swap values — primitives never change */
32 --color-surface-default: hsl(222, 47%, 11%);
33 --color-surface-alt: hsl(222, 47%, 15%);
34 --color-text-primary: hsl(220, 20%, 96%);
35 --color-action-primary: hsl(220, 90%, 65%); /* lighter in dark */
36 }
37}In Figma Dev Mode, the Inspect panel shows which tokens are applied to each property of a selected element. Instead of showing a raw hex value, it shows the token name.
Dev Mode token display:
Reading token references:
Token path → CSS property name convention:
'color/action/primary' becomes '--color-action-primary' (slashes → hyphens, prefix with '--')
Elements without tokens (still using raw values) appear as raw hex/pixel numbers without a chip — these represent design decisions that haven't been tokenized yet. As a developer, these map to hardcoded values or require clarification from the designer.
1/* From Figma Dev Mode inspection of a Button:
2 Fill: color/action/primary (chip shows hsl(220,90%,56%))
3 Border: color/action/primary
4 Corner radius: radius/md (chip shows 8px)
5 Padding: spacing/component/md (chip shows 10px 20px)
6 Font size: raw value 15px (no token — hardcoded in spec)
7 Font weight: raw value 600 (no token)
8*/
9.btn-primary {
10 background: var(--color-action-primary);
11 border: 1.5px solid var(--color-action-primary);
12 border-radius: var(--radius-component-md);
13 padding: var(--space-component-md); /* will need to check if this is 10px 20px */
14 font-size: 0.9375rem; /* hardcoded — no token exists yet */
15 font-weight: 600; /* hardcoded — no token exists yet */
16 color: white; /* white is often not tokenized for solid buttons */
17}Tokens Studio (formerly Figma Tokens) is the most widely used plugin for managing and exporting design tokens from Figma. It stores tokens in a JSON file format compatible with Style Dictionary.
Tokens Studio setup workflow:
Tokens Studio JSON structure:
1{
2 "color": {
3 "primitive": {
4 "blue": {
5 "500": { "value": "hsl(220, 90%, 56%)", "type": "color" },
6 "600": { "value": "hsl(220, 90%, 46%)", "type": "color" }
7 },
8 "gray": {
9 "50": { "value": "hsl(220, 20%, 97%)", "type": "color" },
10 "900": { "value": "hsl(220, 30%, 10%)", "type": "color" }
11 }
12 },
13 "action": {
14 "primary": { "value": "{color.primitive.blue.500}", "type": "color" },
15 "primary-hover": { "value": "{color.primitive.blue.600}", "type": "color" }
16 },
17 "surface": {
18 "default": {
19 "value": "#ffffff",
20 "type": "color",
21 "$extensions": { "dark": "hsl(222, 47%, 11%)" }
22 }
23 }
24 },
25 "spacing": {
26 "2": { "value": "0.5rem", "type": "dimension" },
27 "4": { "value": "1rem", "type": "dimension" },
28 "6": { "value": "1.5rem", "type": "dimension" }
29 },
30 "radius": {
31 "sm": { "value": "0.375rem", "type": "dimension" },
32 "md": { "value": "0.5rem", "type": "dimension" },
33 "lg": { "value": "0.75rem", "type": "dimension" }
34 }
35}Style Dictionary transform to CSS:
1# Install Style Dictionary
2npm install --save-dev style-dictionary
3
4# style-dictionary.config.js
5module.exports = {
6 source: ['tokens/**/*.json'],
7 platforms: {
8 css: {
9 transformGroup: 'css',
10 prefix: '',
11 buildPath: 'src/styles/',
12 files: [{
13 destination: 'tokens.css',
14 format: 'css/variables',
15 selector: ':root',
16 }]
17 }
18 }
19};
20
21# Run transform
22npx style-dictionary buildGenerated tokens.css:
1/* Auto-generated by Style Dictionary — do not edit manually */
2:root {
3 --color-primitive-blue-500: hsl(220, 90%, 56%);
4 --color-primitive-blue-600: hsl(220, 90%, 46%);
5 --color-action-primary: var(--color-primitive-blue-500);
6 --color-action-primary-hover: var(--color-primitive-blue-600);
7 --color-surface-default: #ffffff;
8 --spacing-2: 0.5rem;
9 --spacing-4: 1rem;
10 --spacing-6: 1.5rem;
11 --radius-sm: 0.375rem;
12 --radius-md: 0.5rem;
13 --radius-lg: 0.75rem;
14}Figma Variables support modes — different sets of values for the same token. The most common modes are Light and Dark, but modes can also represent brand themes (Default, Enterprise, Partner).
In Figma: Variables panel → Collection → Modes tab
Each mode shows the value for every token in that mode. Semantic tokens have different values per mode; primitive tokens have the same value in every mode (primitives never change, only their semantic references do).
Reading mode-specific values:
Implementing modes in CSS:
1/* Method 1: prefers-color-scheme media query (automatic) */
2@media (prefers-color-scheme: dark) {
3 :root {
4 --color-surface-default: hsl(222, 47%, 11%);
5 --color-surface-alt: hsl(222, 47%, 15%);
6 --color-text-primary: hsl(220, 20%, 96%);
7 --color-action-primary: hsl(220, 90%, 65%);
8 }
9}
10
11/* Method 2: data-theme attribute (user-controlled) */
12[data-theme='dark'] {
13 --color-surface-default: hsl(222, 47%, 11%);
14 --color-surface-alt: hsl(222, 47%, 15%);
15 --color-text-primary: hsl(220, 20%, 96%);
16 --color-action-primary: hsl(220, 90%, 65%);
17}
18
19/* Method 3: both (recommended — auto + manual override) */
20@media (prefers-color-scheme: dark) {
21 :root:not([data-theme='light']) {
22 --color-surface-default: hsl(222, 47%, 11%);
23 }
24}
25[data-theme='dark'] {
26 --color-surface-default: hsl(222, 47%, 11%);
27}
28[data-theme='light'] {
29 --color-surface-default: #ffffff; /* explicit light override */
30}CI integration: when a designer updates a token value in Figma, Tokens Studio commits the change to the tokens.json in the GitHub repo, triggering a CI build that runs Style Dictionary to regenerate tokens.css and opens a PR with the CSS diff for developer review.
A complete CSS token system with primitive and semantic layers, light and dark modes, and component usage examples that reference only semantic tokens.
1<!-- Complete token system — tokens.css generated from Style Dictionary -->
2<style>
3 /* =============================================
4 PRIMITIVE TOKENS — raw values
5 Never reference these directly in components
6 ============================================= */
7 :root {
8 /* Blue scale */
9 --prim-blue-50: hsl(220, 100%, 97%);
10 --prim-blue-100: hsl(220, 96%, 93%);
11 --prim-blue-500: hsl(220, 90%, 56%);
12 --prim-blue-600: hsl(220, 90%, 46%);
13 --prim-blue-700: hsl(220, 88%, 37%);
14 --prim-blue-900: hsl(220, 80%, 15%);
15
16 /* Green scale */
17 --prim-green-50: hsl(142, 72%, 95%);
18 --prim-green-600: hsl(142, 72%, 35%);
19
20 /* Red scale */
21 --prim-red-50: hsl(0, 84%, 97%);
22 --prim-red-600: hsl(0, 84%, 47%);
23
24 /* Gray scale */
25 --prim-gray-50: hsl(220, 20%, 97%);
26 --prim-gray-100: hsl(220, 20%, 95%);
27 --prim-gray-200: hsl(220, 16%, 90%);
28 --prim-gray-400: hsl(220, 12%, 62%);
29 --prim-gray-600: hsl(220, 16%, 40%);
30 --prim-gray-900: hsl(220, 30%, 10%);
31
32 /* Spacing */
33 --prim-space-1: 0.25rem; --prim-space-2: 0.5rem;
34 --prim-space-3: 0.75rem; --prim-space-4: 1rem;
35 --prim-space-5: 1.25rem; --prim-space-6: 1.5rem;
36 --prim-space-8: 2rem; --prim-space-12: 3rem;
37 --prim-space-16: 4rem;
38
39 /* Radius */
40 --prim-radius-sm: 0.375rem;
41 --prim-radius-md: 0.5rem;
42 --prim-radius-lg: 0.75rem;
43 --prim-radius-xl: 1rem;
44 --prim-radius-full: 9999px;
45
46 /* Shadow */
47 --prim-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.04);
48 --prim-shadow-md: 0 4px 6px rgb(0 0 0 / 0.07), 0 2px 4px rgb(0 0 0 / 0.04);
49 --prim-shadow-lg: 0 10px 15px rgb(0 0 0 / 0.1), 0 4px 6px rgb(0 0 0 / 0.05);
50 }
51
52 /* =============================================
53 SEMANTIC TOKENS — light mode
54 Always reference these in component CSS
55 ============================================= */
56 :root {
57 /* Background surfaces */
58 --color-bg: white;
59 --color-bg-alt: var(--prim-gray-50);
60 --color-surface: white;
61 --color-surface-alt: var(--prim-gray-100);
62
63 /* Borders */
64 --color-border: var(--prim-gray-200);
65 --color-border-strong: var(--prim-gray-400);
66
67 /* Text */
68 --color-text-primary: var(--prim-gray-900);
69 --color-text-secondary: var(--prim-gray-600);
70 --color-text-muted: var(--prim-gray-400);
71
72 /* Action / Brand */
73 --color-action-primary: var(--prim-blue-500);
74 --color-action-primary-hover: var(--prim-blue-600);
75 --color-action-primary-active: var(--prim-blue-700);
76 --color-action-primary-subtle: var(--prim-blue-50);
77 --color-action-primary-text: var(--prim-blue-900);
78
79 /* Feedback */
80 --color-success: var(--prim-green-600);
81 --color-success-subtle: var(--prim-green-50);
82 --color-danger: var(--prim-red-600);
83 --color-danger-subtle: var(--prim-red-50);
84
85 /* Spacing semantic */
86 --space-xs: var(--prim-space-1);
87 --space-sm: var(--prim-space-2);
88 --space-md: var(--prim-space-4);
89 --space-lg: var(--prim-space-6);
90 --space-xl: var(--prim-space-8);
91 --space-2xl: var(--prim-space-12);
92
93 /* Radius semantic */
94 --radius-sm: var(--prim-radius-sm);
95 --radius-md: var(--prim-radius-md);
96 --radius-lg: var(--prim-radius-lg);
97 --radius-xl: var(--prim-radius-xl);
98 --radius-pill: var(--prim-radius-full);
99
100 /* Shadow semantic */
101 --shadow-card: var(--prim-shadow-sm);
102 --shadow-dropdown: var(--prim-shadow-md);
103 --shadow-modal: var(--prim-shadow-lg);
104 }
105
106 /* Dark mode — only semantic tokens change */
107 @media (prefers-color-scheme: dark) {
108 :root {
109 --color-bg: hsl(222, 47%, 10%);
110 --color-bg-alt: hsl(222, 47%, 12%);
111 --color-surface: hsl(222, 47%, 15%);
112 --color-surface-alt: hsl(222, 47%, 19%);
113 --color-border: hsl(222, 30%, 24%);
114 --color-border-strong: hsl(222, 20%, 40%);
115 --color-text-primary: hsl(220, 20%, 96%);
116 --color-text-secondary: hsl(220, 15%, 70%);
117 --color-text-muted: hsl(220, 10%, 52%);
118 --color-action-primary: hsl(220, 90%, 65%);
119 --color-action-primary-hover: hsl(220, 90%, 72%);
120 --color-action-primary-subtle: hsl(220, 60%, 18%);
121 --color-success-subtle: hsl(142, 40%, 14%);
122 --color-danger-subtle: hsl(0, 50%, 14%);
123 }
124 }
125
126 /* =============================================
127 COMPONENTS — reference only semantic tokens
128 ============================================= */
129 *, *::before, *::after { box-sizing: border-box; }
130 body { margin: 0; background: var(--color-bg);
131 color: var(--color-text-primary); font-family: system-ui, sans-serif;
132 padding: 2rem; }
133
134 /* Button — all semantic token references */
135 .btn {
136 display: inline-flex; align-items: center; justify-content: center;
137 gap: var(--space-sm);
138 padding: var(--space-sm) var(--space-md);
139 border-radius: var(--radius-md);
140 font-size: 0.9375rem; font-weight: 600; font-family: inherit;
141 border: 1.5px solid transparent; cursor: pointer;
142 transition: 150ms ease; text-decoration: none;
143 }
144 .btn-primary {
145 background: var(--color-action-primary);
146 border-color: var(--color-action-primary);
147 color: white;
148 }
149 .btn-primary:hover { background: var(--color-action-primary-hover);
150 border-color: var(--color-action-primary-hover); }
151
152 /* Card — all semantic token references */
153 .card {
154 background: var(--color-surface);
155 border: 1px solid var(--color-border);
156 border-radius: var(--radius-lg);
157 box-shadow: var(--shadow-card);
158 padding: var(--space-lg);
159 }
160 .card-title { color: var(--color-text-primary); font-weight: 700; margin: 0 0 0.375rem; }
161 .card-body { color: var(--color-text-secondary); font-size: 0.9375rem; margin: 0; }
162</style>
163
164<div class="card" style="max-width:360px;">
165 <h2 class="card-title">Design Tokens Demo</h2>
166 <p class="card-body">This card uses only semantic CSS custom properties. Toggle your system to dark mode to see it automatically adapt.</p>
167 <div style="margin-top:var(--space-md);display:flex;gap:var(--space-sm);">
168 <button class="btn btn-primary" type="button">Primary action</button>
169 </div>
170</div>This token system follows the two-layer architecture: primitive tokens (--prim-*) define all raw values and are never referenced in components. Semantic tokens reference primitives and are the only tokens components use. When dark mode activates via prefers-color-scheme, only semantic tokens change — primitive values remain constant. The card and button use exclusively semantic tokens (--color-surface, --color-border, --radius-lg, --space-lg) which means they adapt to dark mode automatically with zero component-level CSS changes. If the brand color changes from blue-500 to a different hue, updating --prim-blue-500 propagates everywhere.
The next lesson covers Figma Typography Styles and text component architecture — how to define and maintain text styles that map one-to-one with CSS type scale custom properties.
Your progress is saved in this browser, on this device only.