What this guide covers
- Use styles for raw swatches and variables for semantic roles.
- Name tokens by job, not by hue.
- Use modes for light and dark instead of duplicate collections.
- Keep the same token names in Figma and code.
- Audit drift with a rendered component, not just a value diff.
Live theme
Preview a complete light and dark theme
Choose a sample palette, switch themes, and copy the CSS variables a real component library consumes.
A theme that holds up in both modes.
Surfaces, borders and text pairs are generated from the same palette. Switch the theme and compare the hierarchy.
Semantic roles
Contrast checks
Copy the theme
:root {
--color-primary: #6B4E9A;
--color-primary-foreground: #FFFFFF;
--color-secondary: #FFF8F0;
--color-accent: #FFD166;
--color-neutral: #64748B;
--color-background: #FFF8F0;
--color-surface: #FFFCF8;
--color-text: #111827;
--color-muted-text: #111827;
--color-border: #D4D3D4;
--color-success: #16A34A;
--color-warning: #FFD166;
--color-danger: #F27E59;
}
[data-theme="dark"] {
--color-background: #1A1A2E;
--color-surface: #111225;
--color-text: #FFFFFF;
--color-muted-text: #A2ACB9;
--color-border: #3B4257;
--color-primary: #A793C8;
--color-primary-foreground: #111827;
}:root {
--background: #FFF8F0;
--foreground: #111827;
--card: #FFFCF8;
--card-foreground: #111827;
--primary: #6B4E9A;
--primary-foreground: #FFFFFF;
--secondary: #FFF8F0;
--secondary-foreground: #111827;
--accent: #FFD166;
--accent-foreground: #111827;
--muted: #FFFAF4;
--muted-foreground: #111827;
--border: #D4D3D4;
--destructive: #F27E59;
--destructive-foreground: #FFFFFF;
--ring: #6B4E9A;
}
.dark {
--background: #1A1A2E;
--foreground: #FFFFFF;
--card: #111225;
--card-foreground: #FFFFFF;
--primary: #A793C8;
--primary-foreground: #111827;
--secondary: #FFF8F0;
--secondary-foreground: #111827;
--accent: #FFD166;
--accent-foreground: #111827;
--muted: #16162A;
--muted-foreground: #A2ACB9;
--border: #3B4257;
--destructive: #F27E59;
--destructive-foreground: #FFFFFF;
--ring: #A793C8;
}{
"color": {
"primary": {
"value": "#6B4E9A",
"type": "color"
},
"secondary": {
"value": "#FFF8F0",
"type": "color"
},
"accent": {
"value": "#FFD166",
"type": "color"
},
"neutral": {
"value": "#64748B",
"type": "color"
},
"background": {
"value": "#FFF8F0",
"type": "color"
},
"surface": {
"value": "#FFFCF8",
"type": "color"
},
"text": {
"value": "#111827",
"type": "color"
},
"mutedText": {
"value": "#111827",
"type": "color"
},
"success": {
"value": "#16A34A",
"type": "color"
},
"warning": {
"value": "#FFD166",
"type": "color"
},
"danger": {
"value": "#F27E59",
"type": "color"
}
},
"dark": {
"background": {
"value": "#1A1A2E",
"type": "color"
},
"surface": {
"value": "#111225",
"type": "color"
},
"text": {
"value": "#FFFFFF",
"type": "color"
},
"mutedText": {
"value": "#A2ACB9",
"type": "color"
}
}
}{
"primary": "oklch(48.97% 0.1205 299.6)",
"secondary": "oklch(98.24% 0.0130 71.3)",
"accent": "oklch(88.03% 0.1348 86.1)",
"neutral": "oklch(55.44% 0.0407 257.4)",
"background": "oklch(98.24% 0.0130 71.3)",
"foreground": "oklch(21.01% 0.0318 264.7)",
"surface": "oklch(99.23% 0.0062 75.4)",
"success": "oklch(62.71% 0.1699 149.2)",
"warning": "oklch(88.03% 0.1348 86.1)",
"danger": "oklch(71.71% 0.1518 38.3)"
}
Styles, variables and tokens are different tools
A style is a reusable color swatch. It is simple and familiar, but it stores one value. A variable can store multiple values by mode, which makes it a better fit for light and dark themes.
A design token is the larger idea: a named decision that can move between Figma, CSS, documentation and a token platform. The token name is the stable part; the storage format can change.
A practical setup uses both. Raw swatches can live as styles or a raw variable collection. Semantic roles live as variables with light and dark modes. The codebase consumes the same semantic names.
Name tokens by job
The most common handoff failure is a token named after its appearance. `blue-500` becomes wrong the moment the brand changes. `color/action/primary` survives because it describes the job.
Use a small vocabulary: background, surface, surface-elevated, text, text-muted, border, primary, primary-foreground, accent, success, warning, danger. Add a token when a real component needs it.
Keep raw ramps separate. `neutral/100` through `neutral/900` are values; `color/text` is a role that references one of them. That separation lets you adjust the ramp without renaming every component.
- Role first: action, surface, text, border, status.
- Raw ramps second: neutral, brand, accent.
- No hue names in semantic tokens.
- No component names unless the token is component-scoped.
Use modes for light and dark
If your Figma plan supports variable modes, create a Light and a Dark mode in the same collection. Each semantic token gets a value in both modes. Components then switch modes without duplicating the design.
The dark values should be chosen for dark surfaces. Do not invert the light values. The primary may need a different lightness, and the border may need to become a light line instead of a dark one.
If your plan does not support modes, create two collections with identical token names and document which one is active. The naming consistency is more important than the storage mechanism.
Importing and exporting tokens
Figma's native variable tools can create values manually. Token plugins can import a JSON file and create variables in bulk. The exact schema depends on the plugin, so start with a small set and verify the result.
A useful export shape is a nested object with a value and a type for each token. PhotoColors generates that shape, and it can be adapted to common token plugins.
Keep the JSON in the repository if code is the source of truth, or in a shared design-system location if Figma is the source. The important rule is one source, not two.
Handoff to code
The code tokens should use the same semantic names as the Figma variables. A CSS variable named `--color-primary` and a Figma variable named `color/primary` are easy to map. Two unrelated naming systems are not.
Export the raw values and the semantic mapping together. The code needs both: the raw step values and the role definitions. If only the raw ramp is exported, the semantic decisions are lost.
Document the foreground pairs. A designer may assume that a primary token is safe for text, but the code needs `primary-foreground` as a separate value.
Audit drift with rendered components
A token diff can tell you that a value changed, but not whether the interface still works. Audit a rendered button, card, input, dialog and destructive action in both modes.
Compare the actual colors with the documented values. If a component is using a local override instead of the token, the token system is not being followed. Fix the component, not the token.
Run the audit after every brand change and after every dependency upgrade. A small change in a component library can alter how a token is applied.
Keep a short evidence page for the audit: a screenshot of the component in light mode, a screenshot in dark mode, the token names used, and the measured contrast values. This is more useful than a spreadsheet of HEX codes because it shows the context in which the values are used.
If a token is correct but the component still looks wrong, inspect the component's local styles. A hard-coded color, an opacity value or a shadow can override the token without changing the token itself. Those overrides are the real source of drift, and they should be removed or converted into documented component tokens.
Finally, make the audit repeatable. Add it to the design-system checklist, assign an owner, and run it whenever a theme, a component or a brand color changes. Drift is not a one-time cleanup; it is a maintenance habit.
A Figma token checklist
Before declaring the system done, confirm that every semantic role has a light and dark value, every text pair has been measured, and every raw step used by a component is documented.
Check that the token names match between Figma and code. Check that the focus ring and border are visible on every surface. Check that status colors are distinguishable and have non-color cues.
Finally, test a real screen with real content. Tokens are a means to an end; the end is a readable, coherent interface.
