Tutorial

Design Tokens vs CSS Variables: What's the Difference?

Learn the key differences between design tokens and CSS variables, when to use each, and how FramingUI bridges both for AI-friendly design systems.

FramingUI Team10 min read

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

  1. Runtime theming: User toggles dark mode → update one variable, entire UI changes
  2. Dynamic values: Calculate spacing or colors based on viewport size
  3. Scoped overrides: Change button color within a specific section
  4. No build step: Works directly in the browser, great for prototyping

CSS Variables Limitations

  1. CSS-only: Can't generate iOS/Android/Flutter design systems from CSS variables
  2. No semantic meaning: A variable named --blue-500 doesn't tell you it's for primary actions
  3. Hard to validate: No type safety, no structure—just strings
  4. Not AI-friendly: AI tools can't understand the intent behind var(--spacing-4) without extra context
  5. 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

  1. Multi-platform design systems: One source → CSS, iOS, Android, Flutter, React Native
  2. Design-to-code handoff: Designers define tokens in Figma → developers import directly
  3. AI code generation: AI reads tokens and generates on-brand components automatically
  4. Version control: Track design changes in Git, review design decisions in PRs
  5. Documentation: Each token has type, description, and metadata
  6. 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?

ScenarioUse ThisWhy
Building a multi-platform productDesign tokensSingle source of truth for web + mobile
Runtime theme switching (dark mode)CSS variablesDynamic, no rebuild needed
AI-generated componentsDesign tokensAI can read structured data + semantic meaning
Design-to-code workflowDesign tokensExport from Figma → import into codebase
Quick prototype with no build stepCSS variablesBrowser-native, no tooling required
Design system with documentationDesign tokensTypes, descriptions, metadata built-in
Dynamic spacing based on viewportCSS variablescalc() 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-bg is actually a valid color
  • Can't generate Android/iOS button styles from this
  • AI doesn't know --btn-bg is 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.

Ready to build with FramingUI?

Build consistent UI with AI-ready design tokens. No more hallucinated colors or spacing.

Try FramingUI
Share

Related Posts