Using design tokens as variables: best practices
Real-world example of implementing design tokens as variables in different tools
Design tokens? You mean variables, right?
At first glance, design tokens and code variables appear identical: both store reusable values. But this similarity hides an important difference in what they do, where they work, and how they evolve.
Code variables exist within a single codebase, are scoped to solve local technical problems, and carry no semantic meaning beyond their immediate context. A variable like buttonBg or $blue-500 is perfectly valid code, but tells you nothing about when to use it, why it exists, or how it relates to the broader system.
Design tokens are design decision that work across all platforms. They capture design choices in a format that works everywhere, use clear names that explain their purpose, and act as a common language between designers and developers.
Without proper governance, variables proliferate uncontrollably. Developers create them ad-hoc, assign arbitrary names, and rarely deprecate outdated ones. Many teams simply rename their existing Sass variables or Figma styles and call them “design tokens,” expecting immediate benefits.
But tokens without semantic structure, clear governance, and automated synchronization are just variables with better-sounding names. They don’t solve the underlying problems. True value comes from treating them as cross-platform elements. This demands three investments:
a rigorous naming strategy that encodes meaning
a clear governance controlling evolution
automation ensuring design-code parity
In this article, we’ll explore real-world examples of implementing design tokens as variables, examine the critical decisions you’ll need to make, and address common problems that arise when working with Figma Variables, CSS Custom Properties, and Sass.
Design Tokens as variables in Figma
Figma has evolved significantly in its support for design tokens through its Variables feature. Variables in Figma allow designers to create reusable values that can be applied across designs and updated globally.
1. Use a Clear Naming Convention
Establish a consistent naming system that follows a hierarchical structure. A common pattern is: category/type/property/variant/state
Example:
color/brand/primary/500
color/semantic/error/default
spacing/component/button/padding-x
typography/heading/h1/font-size
2. Organize Variables into Collections
Group related variables into collections to keep your design system organized. For example:
Color Collection: All color-related variables
Typography Collection: Font families, sizes, and weights
Spacing Collection: Spacing and sizing values
Example structure:
Collection: Colors
├── Brand
│ ├── primary-100
│ ├── primary-500
│ └── primary-900
├── Semantic
│ ├── success
│ ├── error
│ └── warning
└── Neutral
├── gray-50
├── gray-500
└── gray-900
3. Leverage Modes for Theming
Use Figma’s modes feature to create variations of your design tokens for different themes (e.g., light and dark modes).
Example:
Collection: Colors
Mode: Light
- background/primary = #FFFFFF
- text/primary = #000000
Mode: Dark
- background/primary = #1A1A1A
- text/primary = #FFFFFF
4. Use Aliases for Semantic Tokens
Create semantic tokens that reference base tokens. This adds a layer of meaning and makes updates easier.
Example:
Base token: color/blue/500 = #3B82F6
Alias token: color/interactive/primary = {color/blue/500}
Base token: color/red/500 = #EF4444
Alias token: color/feedback/error = {color/red/500}
5. Document Your Variables
Add descriptions to your variables in Figma to help team members understand when and how to use each token.
Example description:
“color/interactive/primary - Use for primary call-to-action buttons, links, and interactive elements that represent the main brand action.”
Using Design Tokens in CSS
In CSS, design tokens are typically implemented using CSS Custom Properties (CSS Variables). These provide native browser support and can be dynamically updated using JavaScript.
Best Practices for CSS Variables
1. Define Variables at the Root Level
Place your global design tokens in the :root selector to make them available throughout your application.
Example:
:root {
/* Color tokens */
--color-primary-500: #3B82F6;
--color-primary-600: #2563EB;
--color-error-500: #EF4444;
/* Spacing tokens */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* Typography tokens */
--font-family-base: ‘Inter’, sans-serif;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-weight-normal: 400;
--font-weight-semibold: 600;
--font-weight-bold: 700;
}
2. Use Semantic Token Layers
Create semantic tokens that reference primitive tokens, making your CSS more maintainable.
Example:
:root {
/* Primitive tokens */
--color-blue-500: #3B82F6;
--color-red-500: #EF4444;
--color-green-500: #10B981;
/* Semantic tokens */
--color-interactive-primary: var(--color-blue-500);
--color-feedback-error: var(--color-red-500);
--color-feedback-success: var(--color-green-500);
}
.button-primary {
background-color: var(--color-interactive-primary);
color: white;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius-md);
}
3. Implement Theme Switching with Data Attributes
Use data attributes or classes to switch between themes dynamically.
Example:
:root {
--color-background: #FFFFFF;
--color-text: #1A1A1A;
}
[data-theme=”dark”] {
--color-background: #1A1A1A;
--color-text: #FFFFFF;
}
body {
background-color: var(--color-background);
color: var(--color-text);
}
4. Group Related Tokens with Prefixes
Use consistent prefixes to group related tokens, making them easier to find and maintain.
Example:
:root {
/* Button tokens */
--button-padding-x: var(--spacing-md);
--button-padding-y: var(--spacing-sm);
--button-border-radius: 0.375rem;
--button-font-weight: var(--font-weight-semibold);
/* Card tokens */
--card-padding: var(--spacing-lg);
--card-border-radius: 0.5rem;
--card-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
5. Use calc() for Derived Values
Create derived tokens using calc() to maintain mathematical relationships.
Example:
:root {
--spacing-base: 1rem;
--spacing-xs: calc(var(--spacing-base) * 0.25);
--spacing-sm: calc(var(--spacing-base) * 0.5);
--spacing-md: var(--spacing-base);
--spacing-lg: calc(var(--spacing-base) * 1.5);
--spacing-xl: calc(var(--spacing-base) * 2);
--spacing-2xl: calc(var(--spacing-base) * 3);
}
Using Design Tokens in Sass
Sass provides powerful features like variables, maps, and functions that make working with design tokens even more flexible. However, it’s important to note that Sass variables are compiled at build time, unlike CSS custom properties which are dynamic.
Best Practices for Sass Variables
1. Organize Tokens in Sass Maps
Use Sass maps to organize related tokens, making them easier to access and iterate over.
Example:
// _tokens.scss
$colors: (
‘primary’: (
100: #DBEAFE,
300: #93C5FD,
500: #3B82F6,
700: #1D4ED8,
900: #1E3A8A
),
‘error’: (
100: #FEE2E2,
500: #EF4444,
900: #7F1D1D
)
);
$spacing: (
‘xs’: 0.25rem,
‘sm’: 0.5rem,
‘md’: 1rem,
‘lg’: 1.5rem,
‘xl’: 2rem
);
$typography: (
‘font-family’: (
‘base’: (’Inter’, sans-serif),
‘heading’: (’Poppins’, sans-serif),
‘mono’: (’Fira Code’, monospace)
),
‘font-size’: (
‘xs’: 0.75rem,
‘sm’: 0.875rem,
‘base’: 1rem,
‘lg’: 1.125rem,
‘xl’: 1.25rem
)
);
2. Create Getter Functions for Token Access
Build utility functions to access tokens from your maps, providing a clean API and error handling.
Example:
// _functions.scss
@function color($name, $variant: 500) {
@if not map-has-key($colors, $name) {
@error “Color ‘#{$name}’ not found in $colors map”;
}
$color-map: map-get($colors, $name);
@if not map-has-key($color-map, $variant) {
@error “Variant ‘#{$variant}’ not found for color ‘#{$name}’”;
}
@return map-get($color-map, $variant);
}
@function spacing($size) {
@if not map-has-key($spacing, $size) {
@error “Spacing ‘#{$size}’ not found in $spacing map”;
}
@return map-get($spacing, $size);
}
// Usage
.button {
background-color: color(’primary’, 500);
padding: spacing(’sm’) spacing(’md’);
color: white;
}
3. Combine Sass Variables with CSS Custom Properties
Get the best of both worlds by using Sass to generate CSS custom properties. This allows you to leverage Sass’s organizational features while maintaining runtime flexibility.
Example:
// Generate CSS custom properties from Sass maps
:root {
@each $name, $variants in $colors {
@each $variant, $value in $variants {
--color-#{$name}-#{$variant}: #{$value};
}
}
@each $size, $value in $spacing {
--spacing-#{$size}: #{$value};
}
}
// Use the generated CSS custom properties
.button {
background-color: var(--color-primary-500);
padding: var(--spacing-sm) var(--spacing-md);
}
4. Create Mixins for Common Token Patterns
Build reusable mixins that apply multiple tokens at once, ensuring consistency across components.
Example:
@mixin heading($level) {
$heading-config: map-get($typography, ‘heading’);
$font-sizes: map-get($heading-config, ‘font-size’);
$font-weights: map-get($heading-config, ‘font-weight’);
$line-heights: map-get($heading-config, ‘line-height’);
font-family: map-get(map-get($typography, ‘font-family’), ‘heading’);
font-size: map-get($font-sizes, $level);
font-weight: map-get($font-weights, $level);
line-height: map-get($line-heights, $level);
color: color(’text’, ‘primary’);
}
@mixin card-base {
background-color: color(’background’, ‘surface’);
border-radius: spacing(’md’);
padding: spacing(’lg’);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
// Usage
h1 {
@include heading(’h1’);
}
.card {
@include card-base;
}
5. Use Partials to Organize Token Files
Split your tokens into logical partial files and import them in the correct order.
Example file structure:
styles/
├── tokens/
│ ├── _colors.scss
│ ├── _spacing.scss
│ ├── _typography.scss
│ ├── _shadows.scss
│ └── _index.scss
├── functions/
│ ├── _color.scss
│ ├── _spacing.scss
│ └── _index.scss
├── mixins/
│ ├── _typography.scss
│ ├── _layout.scss
│ └── _index.scss
└── main.scss
Example _index.scss:
// tokens/_index.scss
@forward ‘colors’;
@forward ‘spacing’;
@forward ‘typography’;
@forward ‘shadows’;
// main.scss
@use ‘tokens’;
@use ‘functions’;
@use ‘mixins’;
6. Generate Utility Classes from Tokens
Automate the creation of utility classes based on your design tokens.
Example in Sass:
// Generate spacing utilities
@each $size, $value in $spacing {
.m-#{$size} { margin: $value; }
.mt-#{$size} { margin-top: $value; }
.mr-#{$size} { margin-right: $value; }
.mb-#{$size} { margin-bottom: $value; }
.ml-#{$size} { margin-left: $value; }
.p-#{$size} { padding: $value; }
.pt-#{$size} { padding-top: $value; }
.pr-#{$size} { padding-right: $value; }
.pb-#{$size} { padding-bottom: $value; }
.pl-#{$size} { padding-left: $value; }
}
// Generates:
// .m-xs { margin: 0.25rem; }
// .mt-sm { margin-top: 0.5rem; }
// etc.
Advanced Considerations and Hard‑Won Lessons
Governance as a versioned API
Treat tokens like public interfaces: assign owners, review breaking changes, and version bundles so consumers can upgrade intentionally without surprises.
Accessibility by construction
Bake WCAG checks into the pipeline so invalid contrast never ships, prefer semantic tokens in components, and support high‑contrast and reduced‑motion themes by default.
Composition and indirection
Structure tokens in three layers—primitives → semantic aliases → component slots—so components depend on meaning, not raw values, and remain themeable.
Performance at runtime
Scope CSS variables thoughtfully (global language vs local component), keep hot paths lean, and audit generated styles to avoid bloated cascades.
Testing and CI
Snapshot the source JSON and generated artifacts, run visual regression per theme and density, and lint naming plus alias integrity to catch drift early.
Release workflow
Propose changes with intent and before–after examples, ship to canary themes, observe telemetry, then promote with clear migration notes and version bumps.
Migration playbook
Provide codemods or alias maps from old → new tokens, stage deprecations over a few releases, and offer adapters so large codebases can adopt incrementally.
Common failure modes
Avoid wiring components to primitives, letting Figma and code naming diverge, or reusing one token for multiple semantics — each creates friction during rebrands.



