The Confusion
If you've been researching design systems, you've probably encountered both terms: design tokens and CSS variables. They seem to do the same thing—store reusable design values. So what's the actual difference?
Many developers use them interchangeably. Articles blur the lines. Tools like Figma Variables and Tokens Studio add to the confusion. You might be wondering: "Are they the same thing? Do I need both? Which one should I use?"
Here's the truth: design tokens and CSS variables are related but fundamentally different concepts. Understanding the distinction will help you build more maintainable design systems and make better decisions for your project.
TL;DR
- Design tokens are platform-agnostic design decisions (JSON, YAML, or any data format)
- CSS variables are a browser implementation technology (
--variable-name: value) - Design tokens can be transformed into CSS variables, Sass variables, iOS/Android constants, etc.
- Use design tokens for design system source of truth
- Use CSS variables for runtime theming and dynamic values
- FramingUI uses design tokens as source and generates CSS variables + AI-readable metadata automatically
What Are CSS Variables?
CSS custom properties (commonly called CSS variables) are a browser feature introduced in 2016. They let you store values and reuse them throughout your stylesheets.
Basic CSS Variables Example
:root {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--spacing-base: 8px;
}
.button {
background: var(--color-primary);
padding: calc(var(--spacing-base) * 2);
}
.card {
border-color: var(--color-secondary);
margin: var(--spacing-base);
}
Key characteristics:
- Runtime values: Calculated by the browser at render time
- CSS-only: Cannot be accessed outside CSS without JavaScript
- Dynamic: Can be changed with JavaScript (
element.style.setProperty('--color-primary', '#red')) - Scoped: Can be redefined per element or selector
- Browser-native: No build step required
When CSS Variables Shine
- Runtime theming: User toggles dark mode → update one variable, entire UI changes
- Dynamic values: Calculate spacing or colors based on viewport size
- Scoped overrides: Change button color within a specific section
- No build step: Works directly in the browser, great for prototyping
CSS Variables Limitations
- CSS-only: Can't generate iOS/Android/Flutter design systems from CSS variables
- No semantic meaning: A variable named
--blue-500doesn't tell you it's for primary actions - Hard to validate: No type safety, no structure—just strings
- Not AI-friendly: AI tools can't understand the intent behind
var(--spacing-4)without extra context - Platform-locked: Can't transform into other formats (JSON, Swift, Kotlin)
What Are Design Tokens?
Design tokens are named design decisions stored in a platform-agnostic format (usually JSON or YAML). They represent the source of truth for your design system and can be transformed into any platform-specific format.
Basic Design Tokens Example
{
"color": {
"primary": {
"value": "#3b82f6",
"type": "color",
"description": "Primary brand color used for CTAs and links"
},
"secondary": {
"value": "#8b5cf6",
"type": "color",
"description": "Secondary accent color"
}
},
"spacing": {
"base": {
"value": "8px",
"type": "dimension"
},
"md": {
"value": "16px",
"type": "dimension"
}
}
}
Key characteristics:
- Platform-agnostic: Not tied to CSS, iOS, Android, or any specific technology
- Structured data: Typed, documented, and organized
- Transformable: Can be converted to CSS, Sass, Swift, Kotlin, XML, etc.
- Semantic: Token names describe purpose (
color.primary) not appearance (color.blue.500) - Tooling-friendly: AI, design tools, and build systems can read and generate tokens
When Design Tokens Shine
- Multi-platform design systems: One source → CSS, iOS, Android, Flutter, React Native
- Design-to-code handoff: Designers define tokens in Figma → developers import directly
- AI code generation: AI reads tokens and generates on-brand components automatically
- Version control: Track design changes in Git, review design decisions in PRs
- Documentation: Each token has type, description, and metadata
- Validation: Enforce naming conventions, contrast ratios, accessibility rules
Design Tokens Structure (W3C Standard)
The W3C Design Tokens Community Group defines a standard format:
{
"token-name": {
"value": "actual-value",
"type": "color | dimension | fontFamily | fontWeight | ...",
"description": "Human-readable explanation",
"$extensions": {
"custom-metadata": "any additional data"
}
}
}
This structure enables:
- Type safety: Tools can validate that colors are valid hex/rgb, dimensions have units, etc.
- Discoverability: Developers and AI can browse all available tokens
- Transformation: Build tools know how to convert each type to platform-specific code
The Relationship: Tokens → Variables
Here's the mental model:
┌─────────────────────┐
│ Design Tokens │ ← Source of truth (JSON/YAML)
│ (JSON) │ Platform-agnostic
└──────────┬──────────┘ Versioned in Git
│ Documented & typed
│
├──────────────────────────┐
│ │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ CSS Variables │ │ iOS/Android │
│ :root { ... } │ │ Constants │
└────────────────┘ └────────────────┘
│ │
▼ ▼
Web Apps Mobile Apps
Design tokens are the source. CSS variables are one possible output format.
Transformation Example
Input (design tokens):
{
"color": {
"primary": {
"value": "#3b82f6",
"type": "color"
}
}
}
Output (CSS variables):
:root {
--color-primary: #3b82f6;
}
Output (iOS Swift):
extension UIColor {
static let primary = UIColor(hex: "3b82f6")
}
Output (Android Kotlin):
<color name="primary">#3b82f6</color>
Same token, different output formats. This is the power of design tokens.
When to Use Which?
| Scenario | Use This | Why |
|---|---|---|
| Building a multi-platform product | Design tokens | Single source of truth for web + mobile |
| Runtime theme switching (dark mode) | CSS variables | Dynamic, no rebuild needed |
| AI-generated components | Design tokens | AI can read structured data + semantic meaning |
| Design-to-code workflow | Design tokens | Export from Figma → import into codebase |
| Quick prototype with no build step | CSS variables | Browser-native, no tooling required |
| Design system with documentation | Design tokens | Types, descriptions, metadata built-in |
| Dynamic spacing based on viewport | CSS variables | calc() and viewport units work natively |
The Ideal Approach: Both
Most modern design systems use design tokens as the source and generate CSS variables as one output format.
Design Tokens (JSON) → Build Step → CSS Variables + Docs + Mobile Constants
This gives you:
- ✅ Single source of truth (design tokens)
- ✅ Runtime theming (CSS variables)
- ✅ Multi-platform support (iOS, Android)
- ✅ AI-friendly (structured data)
- ✅ Type safety (validated tokens)
How FramingUI Handles Both
FramingUI uses design tokens as the foundation and automatically generates everything you need:
1. Define Tokens Once (JSON)
{
"color": {
"primary": {
"50": { "value": "#eff6ff", "type": "color" },
"500": { "value": "#3b82f6", "type": "color" },
"900": { "value": "#1e3a8a", "type": "color" }
}
}
}
2. FramingUI Generates CSS Variables
:root {
--color-primary-50: #eff6ff;
--color-primary-500: #3b82f6;
--color-primary-900: #1e3a8a;
}
[data-theme="dark"] {
--color-primary-50: #1e3a8a;
--color-primary-500: #3b82f6;
--color-primary-900: #eff6ff;
}
3. AND Tailwind Classes
// tailwind.config.js
module.exports = {
theme: {
colors: {
primary: {
50: 'var(--color-primary-50)',
500: 'var(--color-primary-500)',
900: 'var(--color-primary-900)',
}
}
}
}
4. AND AI-Readable Metadata
{
"tools": [
{
"name": "list_design_tokens",
"description": "Get all available design tokens (colors, spacing, typography)",
"inputSchema": { "type": "object", "properties": {} }
}
]
}
When Claude or Cursor calls this MCP tool, it gets structured token data and can generate components like:
// AI generates this using your actual tokens
<Button className="bg-primary-500 hover:bg-primary-600 px-4 py-2">
Click me
</Button>
No hardcoded values. No manual replacement. AI reads your design tokens and uses them automatically.
Common Misconceptions
"CSS Variables ARE Design Tokens"
Not quite. CSS variables are a runtime implementation. Design tokens are the source data. You can generate CSS variables FROM design tokens, but raw CSS variables lack the structure, types, and cross-platform capabilities of design tokens.
"I Only Need CSS Variables"
This works if you only build web apps and don't care about:
- AI code generation (AI can't read CSS files easily)
- Multi-platform (iOS/Android need different formats)
- Design tool integration (Figma can't export to raw CSS variables)
- Validation and documentation (CSS variables are just strings)
For solo web projects or quick prototypes, CSS variables alone are fine. For scalable design systems, design tokens are essential.
"Design Tokens Require Complex Tooling"
Not anymore. FramingUI makes it as simple as:
npx framingui init
Define tokens in JSON → FramingUI generates CSS, Tailwind, docs, and AI integration automatically.
Real-World Example: Building a Button
Approach 1: CSS Variables Only
:root {
--btn-bg: #3b82f6;
--btn-text: white;
--btn-padding: 12px 24px;
}
.button {
background: var(--btn-bg);
color: var(--btn-text);
padding: var(--btn-padding);
}
Problems:
- Can't validate
--btn-bgis actually a valid color - Can't generate Android/iOS button styles from this
- AI doesn't know
--btn-bgis for buttons (could be anything) - No documentation or semantic meaning
Approach 2: Design Tokens → CSS Variables
tokens.json:
{
"component": {
"button": {
"primary": {
"background": {
"value": "{color.primary.500}",
"type": "color",
"description": "Primary button background"
},
"text": {
"value": "{color.white}",
"type": "color"
},
"padding": {
"value": "{spacing.md} {spacing.lg}",
"type": "dimension"
}
}
}
}
}
Generated CSS:
:root {
--component-button-primary-background: #3b82f6;
--component-button-primary-text: white;
--component-button-primary-padding: 12px 24px;
}
.button-primary {
background: var(--component-button-primary-background);
color: var(--component-button-primary-text);
padding: var(--component-button-primary-padding);
}
Benefits:
- ✅ Tokens reference other tokens (
{color.primary.500}) - ✅ Type-safe (color, dimension validated)
- ✅ Documented (description field)
- ✅ Can generate iOS/Android/Flutter versions
- ✅ AI can read token structure and understand button styling
Migration Path: CSS Variables → Design Tokens
Already using CSS variables? Here's how to migrate:
Step 1: Extract Variables to JSON
Before (CSS):
:root {
--color-blue-500: #3b82f6;
--spacing-md: 16px;
}
After (JSON):
{
"color": {
"blue": {
"500": { "value": "#3b82f6", "type": "color" }
}
},
"spacing": {
"md": { "value": "16px", "type": "dimension" }
}
}
Step 2: Add Semantic Aliases
{
"color": {
"primary": {
"value": "{color.blue.500}",
"type": "color",
"description": "Primary brand color"
}
}
}
Step 3: Generate CSS Variables from Tokens
Use FramingUI or Style Dictionary:
npx framingui build
Output: Same CSS variables as before, but now generated from structured tokens.
Step 4: Extend to Other Platforms
Now you can generate mobile tokens:
npx framingui build --platform ios
npx framingui build --platform android
Same design system, multiple platforms.
Conclusion
Design tokens and CSS variables serve different purposes:
- Design tokens = source of truth (structured, platform-agnostic, AI-friendly)
- CSS variables = runtime implementation (dynamic, browser-native, themeable)
Use design tokens when:
- Building design systems for multiple platforms
- Enabling AI code generation
- Integrating with Figma or design tools
- You need documentation and type safety
Use CSS variables when:
- Quick prototypes with no build step
- Runtime theming (dark mode toggles)
- Dynamic calculations (
calc(), viewport units)
Best practice: Define design tokens as source → generate CSS variables + other formats automatically.
FramingUI does this out of the box. You get structured tokens, CSS variables, Tailwind classes, TypeScript types, AND AI integration—all from a single source of truth.
Want to see it in action? Try the FramingUI playground or run:
npx framingui init
Your design system (and AI code generation) will thank you.