How-to

CSS Custom Properties for Design Tokens: Complete Setup Guide

Complete guide to implementing design tokens with CSS custom properties. Covers organization, naming, theming, and TypeScript integration.

FramingUI Team12 min read

You've decided to use design tokens. Now you need to choose how to implement them. CSS custom properties (CSS variables) are the most straightforward option for web projects, but there's a right way and several wrong ways to do it.

This guide shows you how to organize, name, and use CSS custom properties as design tokens—with code you can copy and adapt today.

Why CSS Custom Properties?

CSS custom properties have native browser support, work with any framework, and can be updated at runtime. They're perfect for design tokens because:

Runtime theming: Change a token value, and every reference updates immediately

/* Change theme by updating one variable */
:root {
  --color-background: white;
}

[data-theme="dark"] {
  --color-background: black;
}

Cascade: Variables inherit through the DOM

<div style="--spacing-scale: 1.5">
  <!-- All children inherit the scaled spacing -->
  <button>Larger spacing</button>
</div>

No build step: Works directly in the browser

<style>
  :root { --color-primary: blue; }
  button { background: var(--color-primary); }
</style>

Interop with JavaScript: Read and update from JS

document.documentElement.style.setProperty('--color-primary', 'red');

The main limitation: no type safety without additional tooling. We'll address that later.

Token Organization Strategy

Don't dump all tokens into one file. Organize them by category.

File Structure

tokens/
├── index.css          # Imports all token files
├── color.css          # Color tokens
├── spacing.css        # Spacing tokens
├── typography.css     # Typography tokens
├── border.css         # Border tokens
├── shadow.css         # Shadow tokens
├── animation.css      # Animation tokens
└── themes/
    ├── light.css      # Light theme overrides
    └── dark.css       # Dark theme overrides

This structure:

  • Makes tokens easy to find
  • Allows selective imports
  • Separates concerns cleanly
  • Makes theming straightforward

tokens/index.css

/* Import base tokens */
@import './color.css';
@import './spacing.css';
@import './typography.css';
@import './border.css';
@import './shadow.css';
@import './animation.css';

/* Default theme is light */
@import './themes/light.css';

/* Dark theme applied via data attribute */
@import './themes/dark.css';

Import this one file in your app, and all tokens load.

Implementing Color Tokens

Colors are the most complex tokens because they need semantic naming, primitive/application separation, and theme support.

tokens/color.css

:root {
  /* ============================================
     PRIMITIVE COLORS
     Raw color values. Not used directly in components.
     ============================================ */
  
  /* Neutral palette */
  --primitive-neutral-50:  hsl(0 0% 98%);
  --primitive-neutral-100: hsl(0 0% 96%);
  --primitive-neutral-200: hsl(0 0% 92%);
  --primitive-neutral-300: hsl(0 0% 88%);
  --primitive-neutral-400: hsl(0 0% 70%);
  --primitive-neutral-500: hsl(0 0% 50%);
  --primitive-neutral-600: hsl(0 0% 40%);
  --primitive-neutral-700: hsl(0 0% 30%);
  --primitive-neutral-800: hsl(0 0% 15%);
  --primitive-neutral-900: hsl(0 0% 9%);
  
  /* Blue palette */
  --primitive-blue-400: hsl(215 100% 65%);
  --primitive-blue-500: hsl(215 100% 50%);
  --primitive-blue-600: hsl(215 100% 45%);
  --primitive-blue-700: hsl(215 100% 35%);
  
  /* Red palette */
  --primitive-red-400: hsl(0 84% 65%);
  --primitive-red-500: hsl(0 84% 60%);
  --primitive-red-600: hsl(0 84% 50%);
  
  /* Green palette */
  --primitive-green-400: hsl(142 76% 45%);
  --primitive-green-500: hsl(142 76% 36%);
  --primitive-green-600: hsl(142 76% 30%);
  
  /* Yellow palette */
  --primitive-yellow-400: hsl(45 93% 58%);
  --primitive-yellow-500: hsl(45 93% 47%);
  --primitive-yellow-600: hsl(45 93% 37%);
  
  /* ============================================
     APPLICATION COLORS - TEXT
     Used directly in components.
     ============================================ */
  
  --color-text-primary:   var(--primitive-neutral-900);
  --color-text-secondary: var(--primitive-neutral-600);
  --color-text-tertiary:  var(--primitive-neutral-500);
  --color-text-disabled:  var(--primitive-neutral-400);
  --color-text-inverse:   var(--primitive-neutral-50);
  
  --color-text-error:   var(--primitive-red-500);
  --color-text-warning: var(--primitive-yellow-600);
  --color-text-success: var(--primitive-green-600);
  --color-text-info:    var(--primitive-blue-500);
  
  /* ============================================
     APPLICATION COLORS - BACKGROUND
     ============================================ */
  
  --color-background-surface:   hsl(0 0% 100%);
  --color-background-subtle:    var(--primitive-neutral-50);
  --color-background-muted:     var(--primitive-neutral-100);
  --color-background-overlay:   hsla(0 0% 0% / 0.5);
  
  /* ============================================
     APPLICATION COLORS - BORDER
     ============================================ */
  
  --color-border-default: var(--primitive-neutral-300);
  --color-border-strong:  var(--primitive-neutral-400);
  --color-border-subtle:  var(--primitive-neutral-200);
  
  --color-border-error:   var(--primitive-red-500);
  --color-border-warning: var(--primitive-yellow-500);
  --color-border-success: var(--primitive-green-500);
  
  /* ============================================
     APPLICATION COLORS - INTERACTIVE
     ============================================ */
  
  --color-interactive-primary:          var(--primitive-blue-500);
  --color-interactive-primary-hover:    var(--primitive-blue-600);
  --color-interactive-primary-active:   var(--primitive-blue-700);
  --color-interactive-primary-disabled: var(--primitive-blue-400);
  
  --color-interactive-secondary:          transparent;
  --color-interactive-secondary-hover:    var(--primitive-neutral-100);
  --color-interactive-secondary-active:   var(--primitive-neutral-200);
  --color-interactive-secondary-disabled: transparent;
  
  /* ============================================
     APPLICATION COLORS - SPECIAL
     ============================================ */
  
  --color-focus-ring: var(--primitive-blue-500);
  --color-selection-background: var(--primitive-blue-400);
  --color-selection-text: var(--primitive-neutral-50);
}

Notice the structure:

  • Primitives at the top (raw values)
  • Application tokens reference primitives
  • Semantic names describe purpose, not appearance
  • Comments create clear sections

Using Color Tokens

/* ❌ Don't use primitives directly */
.button {
  background: var(--primitive-blue-500);
}

/* ✅ Use application tokens */
.button {
  background: var(--color-interactive-primary);
}

.button:hover {
  background: var(--color-interactive-primary-hover);
}

.button:disabled {
  background: var(--color-interactive-primary-disabled);
  color: var(--color-text-disabled);
}

Implementing Spacing Tokens

Spacing tokens define the rhythm of your UI. Use a consistent scale.

tokens/spacing.css

:root {
  /* ============================================
     SPACING SCALE
     Base scale using rem units (16px base)
     ============================================ */
  
  --spacing-scale-0:  0;
  --spacing-scale-1:  0.25rem;  /* 4px */
  --spacing-scale-2:  0.5rem;   /* 8px */
  --spacing-scale-3:  0.75rem;  /* 12px */
  --spacing-scale-4:  1rem;     /* 16px */
  --spacing-scale-5:  1.25rem;  /* 20px */
  --spacing-scale-6:  1.5rem;   /* 24px */
  --spacing-scale-8:  2rem;     /* 32px */
  --spacing-scale-10: 2.5rem;   /* 40px */
  --spacing-scale-12: 3rem;     /* 48px */
  --spacing-scale-16: 4rem;     /* 64px */
  --spacing-scale-20: 5rem;     /* 80px */
  
  /* ============================================
     INLINE SPACING (horizontal)
     For padding-inline, gap in horizontal layouts
     ============================================ */
  
  --spacing-inline-xs:  var(--spacing-scale-1);
  --spacing-inline-sm:  var(--spacing-scale-2);
  --spacing-inline-md:  var(--spacing-scale-3);
  --spacing-inline-lg:  var(--spacing-scale-4);
  --spacing-inline-xl:  var(--spacing-scale-6);
  --spacing-inline-2xl: var(--spacing-scale-8);
  
  /* ============================================
     STACK SPACING (vertical)
     For padding-block, gap in vertical layouts
     ============================================ */
  
  --spacing-stack-xs:  var(--spacing-scale-1);
  --spacing-stack-sm:  var(--spacing-scale-2);
  --spacing-stack-md:  var(--spacing-scale-4);
  --spacing-stack-lg:  var(--spacing-scale-6);
  --spacing-stack-xl:  var(--spacing-scale-8);
  --spacing-stack-2xl: var(--spacing-scale-12);
  
  /* ============================================
     INSET SPACING (padding all sides)
     For card padding, container padding
     ============================================ */
  
  --spacing-inset-xs:  var(--spacing-scale-2);
  --spacing-inset-sm:  var(--spacing-scale-3);
  --spacing-inset-md:  var(--spacing-scale-4);
  --spacing-inset-lg:  var(--spacing-scale-6);
  --spacing-inset-xl:  var(--spacing-scale-8);
  --spacing-inset-2xl: var(--spacing-scale-12);
  
  /* ============================================
     LAYOUT SPACING (large sections)
     For page sections, major layout areas
     ============================================ */
  
  --spacing-layout-sm: var(--spacing-scale-8);
  --spacing-layout-md: var(--spacing-scale-12);
  --spacing-layout-lg: var(--spacing-scale-16);
  --spacing-layout-xl: var(--spacing-scale-20);
}

Why Different Spacing Categories?

Separating inline, stack, inset, and layout makes intent clear:

/* Vertical spacing between form fields */
.form {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-stack-md);
}

/* Horizontal spacing between buttons */
.button-group {
  display: flex;
  gap: var(--spacing-inline-sm);
}

/* Padding inside a card */
.card {
  padding: var(--spacing-inset-lg);
}

/* Spacing between major page sections */
.page-section {
  margin-block: var(--spacing-layout-lg);
}

The semantic names communicate what spacing is for.

Implementing Typography Tokens

Typography tokens include font family, size, weight, line height, and letter spacing.

tokens/typography.css

:root {
  /* ============================================
     FONT FAMILIES
     ============================================ */
  
  --font-family-sans: 
    -apple-system, BlinkMacSystemFont, 'Segoe UI', 
    Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  
  --font-family-mono: 
    'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', 
    Consolas, 'Courier New', monospace;
  
  /* ============================================
     FONT SIZES
     ============================================ */
  
  --font-size-xs:   0.75rem;   /* 12px */
  --font-size-sm:   0.875rem;  /* 14px */
  --font-size-md:   1rem;      /* 16px */
  --font-size-lg:   1.125rem;  /* 18px */
  --font-size-xl:   1.25rem;   /* 20px */
  --font-size-2xl:  1.5rem;    /* 24px */
  --font-size-3xl:  1.875rem;  /* 30px */
  --font-size-4xl:  2.25rem;   /* 36px */
  --font-size-5xl:  3rem;      /* 48px */
  
  /* ============================================
     LINE HEIGHTS
     ============================================ */
  
  --line-height-tight:  1.25;
  --line-height-normal: 1.5;
  --line-height-loose:  1.75;
  
  /* ============================================
     FONT WEIGHTS
     ============================================ */
  
  --font-weight-normal:  400;
  --font-weight-medium:  500;
  --font-weight-semibold: 600;
  --font-weight-bold:    700;
  
  /* ============================================
     LETTER SPACING
     ============================================ */
  
  --letter-spacing-tight:  -0.025em;
  --letter-spacing-normal: 0;
  --letter-spacing-wide:   0.025em;
  
  /* ============================================
     TYPOGRAPHY PRESETS
     Combine size, weight, line-height, letter-spacing
     ============================================ */
  
  /* Headings */
  --typography-heading-xl: 
    var(--font-weight-bold) var(--font-size-4xl) / var(--line-height-tight) 
    var(--font-family-sans);
  
  --typography-heading-lg: 
    var(--font-weight-bold) var(--font-size-3xl) / var(--line-height-tight) 
    var(--font-family-sans);
  
  --typography-heading-md: 
    var(--font-weight-semibold) var(--font-size-2xl) / var(--line-height-tight) 
    var(--font-family-sans);
  
  --typography-heading-sm: 
    var(--font-weight-semibold) var(--font-size-xl) / var(--line-height-normal) 
    var(--font-family-sans);
  
  /* Body text */
  --typography-body-lg: 
    var(--font-weight-normal) var(--font-size-lg) / var(--line-height-normal) 
    var(--font-family-sans);
  
  --typography-body-md: 
    var(--font-weight-normal) var(--font-size-md) / var(--line-height-normal) 
    var(--font-family-sans);
  
  --typography-body-sm: 
    var(--font-weight-normal) var(--font-size-sm) / var(--line-height-normal) 
    var(--font-family-sans);
  
  /* Labels & UI */
  --typography-label-lg: 
    var(--font-weight-medium) var(--font-size-md) / var(--line-height-normal) 
    var(--font-family-sans);
  
  --typography-label-md: 
    var(--font-weight-medium) var(--font-size-sm) / var(--line-height-normal) 
    var(--font-family-sans);
  
  --typography-label-sm: 
    var(--font-weight-medium) var(--font-size-xs) / var(--line-height-normal) 
    var(--font-family-sans);
  
  /* Captions */
  --typography-caption-md: 
    var(--font-weight-normal) var(--font-size-sm) / var(--line-height-normal) 
    var(--font-family-sans);
  
  --typography-caption-sm: 
    var(--font-weight-normal) var(--font-size-xs) / var(--line-height-normal) 
    var(--font-family-sans);
  
  /* Code */
  --typography-code-md: 
    var(--font-weight-normal) var(--font-size-md) / var(--line-height-normal) 
    var(--font-family-mono);
  
  --typography-code-sm: 
    var(--font-weight-normal) var(--font-size-sm) / var(--line-height-normal) 
    var(--font-family-mono);
}

Using Typography Presets

Typography presets are shorthand properties:

h1 {
  font: var(--typography-heading-xl);
  color: var(--color-text-primary);
}

p {
  font: var(--typography-body-md);
  color: var(--color-text-secondary);
}

label {
  font: var(--typography-label-sm);
  color: var(--color-text-primary);
}

code {
  font: var(--typography-code-sm);
  background: var(--color-background-subtle);
}

Or use individual tokens:

h2 {
  font-family: var(--font-family-sans);
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  letter-spacing: var(--letter-spacing-tight);
}

Implementing Border & Shadow Tokens

tokens/border.css

:root {
  /* Border radius */
  --border-radius-sm:   0.25rem;  /* 4px */
  --border-radius-md:   0.375rem; /* 6px */
  --border-radius-lg:   0.5rem;   /* 8px */
  --border-radius-xl:   0.75rem;  /* 12px */
  --border-radius-2xl:  1rem;     /* 16px */
  --border-radius-full: 9999px;   /* pill shape */
  
  /* Border width */
  --border-width-thin:   1px;
  --border-width-medium: 2px;
  --border-width-thick:  4px;
}

tokens/shadow.css

:root {
  /* Elevation shadows */
  --shadow-elevation-low: 
    0 1px 2px 0 rgba(0, 0, 0, 0.05);
  
  --shadow-elevation-medium: 
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
  
  --shadow-elevation-high: 
    0 10px 15px -3px rgba(0, 0, 0, 0.1),
    0 4px 6px -2px rgba(0, 0, 0, 0.05);
  
  --shadow-elevation-highest: 
    0 20px 25px -5px rgba(0, 0, 0, 0.1),
    0 10px 10px -5px rgba(0, 0, 0, 0.04);
  
  /* Focus ring */
  --shadow-focus: 
    0 0 0 3px var(--color-focus-ring);
  
  /* Inner shadow */
  --shadow-inset: 
    inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
}

Implementing Animation Tokens

tokens/animation.css

:root {
  /* Duration */
  --animation-duration-instant: 0ms;
  --animation-duration-fast:    150ms;
  --animation-duration-normal:  250ms;
  --animation-duration-slow:    350ms;
  --animation-duration-slower:  500ms;
  
  /* Easing */
  --animation-easing-linear:     linear;
  --animation-easing-in:         cubic-bezier(0.4, 0, 1, 1);
  --animation-easing-out:        cubic-bezier(0, 0, 0.2, 1);
  --animation-easing-in-out:     cubic-bezier(0.4, 0, 0.2, 1);
  --animation-easing-bounce:     cubic-bezier(0.68, -0.55, 0.265, 1.55);
  
  /* Common presets */
  --animation-fade-in:   
    opacity var(--animation-duration-normal) var(--animation-easing-out);
  
  --animation-slide-up:  
    transform var(--animation-duration-normal) var(--animation-easing-out);
  
  --animation-button-press: 
    transform var(--animation-duration-fast) var(--animation-easing-in-out);
}

Theming with CSS Custom Properties

The real power: runtime theming.

tokens/themes/light.css

:root, [data-theme="light"] {
  --color-text-primary:   var(--primitive-neutral-900);
  --color-text-secondary: var(--primitive-neutral-600);
  
  --color-background-surface: hsl(0 0% 100%);
  --color-background-subtle:  var(--primitive-neutral-50);
  
  --color-border-default: var(--primitive-neutral-300);
}

tokens/themes/dark.css

[data-theme="dark"] {
  --color-text-primary:   var(--primitive-neutral-50);
  --color-text-secondary: var(--primitive-neutral-400);
  
  --color-background-surface: var(--primitive-neutral-900);
  --color-background-subtle:  var(--primitive-neutral-800);
  
  --color-border-default: var(--primitive-neutral-700);
  
  /* Adjust shadows for dark mode */
  --shadow-elevation-low: 
    0 1px 2px 0 rgba(0, 0, 0, 0.3);
  
  --shadow-elevation-medium: 
    0 4px 6px -1px rgba(0, 0, 0, 0.5),
    0 2px 4px -1px rgba(0, 0, 0, 0.3);
}

Switching Themes

// theme-switcher.js
const setTheme = (theme) => {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
};

// Auto-detect system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');

const initTheme = () => {
  const savedTheme = localStorage.getItem('theme');
  const systemTheme = prefersDark.matches ? 'dark' : 'light';
  setTheme(savedTheme || systemTheme);
};

// Listen for system preference changes
prefersDark.addEventListener('change', (e) => {
  if (!localStorage.getItem('theme')) {
    setTheme(e.matches ? 'dark' : 'light');
  }
});

initTheme();

TypeScript Integration

CSS custom properties have no type safety by default. Fix this with TypeScript.

tokens/types.ts

// Generate from your CSS tokens
export type ColorToken = 
  | 'color-text-primary'
  | 'color-text-secondary'
  | 'color-text-tertiary'
  | 'color-background-surface'
  | 'color-background-subtle'
  | 'color-interactive-primary'
  | 'color-interactive-primary-hover'
  // ... all color tokens
  ;

export type SpacingToken =
  | 'spacing-inline-xs'
  | 'spacing-inline-sm'
  | 'spacing-inline-md'
  | 'spacing-stack-xs'
  | 'spacing-stack-sm'
  | 'spacing-inset-md'
  // ... all spacing tokens
  ;

export type Token = ColorToken | SpacingToken;

// Helper to get CSS variable reference
export const token = (name: Token): string => {
  return `var(--${name})`;
};

Usage with Type Safety

import { token } from './tokens/types';

// ✅ Type-safe
const buttonStyles = {
  backgroundColor: token('color-interactive-primary'),
  padding: `${token('spacing-stack-sm')} ${token('spacing-inline-md')}`,
};

// ❌ TypeScript error - token doesn't exist
const errorStyles = {
  color: token('color-does-not-exist'),
};

Auto-Generate Types from CSS

// scripts/generate-token-types.js
const fs = require('fs');
const path = require('path');

const tokenFiles = [
  'tokens/color.css',
  'tokens/spacing.css',
  'tokens/typography.css',
];

const extractTokens = (cssContent) => {
  const regex = /--([a-z-]+):/g;
  const tokens = [];
  let match;
  
  while ((match = regex.exec(cssContent)) !== null) {
    tokens.push(match[1]);
  }
  
  return tokens;
};

const generateTypes = () => {
  const allTokens = tokenFiles.flatMap(file => {
    const content = fs.readFileSync(file, 'utf-8');
    return extractTokens(content);
  });
  
  const typeDefinition = `
export type Token = 
${allTokens.map(t => `  | '${t}'`).join('\n')};

export const token = (name: Token): string => \`var(--\${name})\`;
  `.trim();
  
  fs.writeFileSync('tokens/types.ts', typeDefinition);
};

generateTypes();

Run this script whenever you update tokens:

// package.json
{
  "scripts": {
    "tokens:generate": "node scripts/generate-token-types.js"
  }
}

Common Pitfalls

Pitfall 1: Using Primitives Directly

❌ Bad:

.button {
  background: var(--primitive-blue-500);
}

✅ Good:

.button {
  background: var(--color-interactive-primary);
}

When you need to change your brand color, application tokens update everywhere. Primitive references don't.

Pitfall 2: Inconsistent Naming

❌ Bad:

:root {
  --color-primary: blue;
  --primaryColor: blue;
  --color_primary: blue;
}

✅ Good:

:root {
  --color-interactive-primary: var(--primitive-blue-500);
}

Pick one convention (kebab-case) and stick to it.

Pitfall 3: No Fallback Values

❌ Fragile:

.button {
  background: var(--color-interactive-primary);
}

If the token isn't defined, background becomes transparent.

✅ Resilient:

.button {
  background: var(--color-interactive-primary, #3B82F6);
}

Fallback ensures something renders even if tokens fail to load.

Pitfall 4: Overusing !important

❌ Bad:

:root {
  --color-text-primary: black !important;
}

!important in custom properties doesn't work how you think—it applies to the property declaration, not the value.

✅ Good:

/* Override in a more specific selector */
[data-theme="dark"] {
  --color-text-primary: white;
}

Real-World Example: Button Component

Putting it all together:

/* Button.module.css */
.button {
  /* Layout */
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-inline-sm);
  padding: var(--spacing-stack-sm, 0.5rem) var(--spacing-inline-md, 0.75rem);
  
  /* Typography */
  font: var(--typography-label-md);
  
  /* Visual */
  background: var(--color-interactive-primary, #3B82F6);
  color: var(--color-text-inverse, white);
  border: none;
  border-radius: var(--border-radius-md, 0.375rem);
  box-shadow: var(--shadow-elevation-low);
  
  /* Interaction */
  cursor: pointer;
  transition: var(--animation-button-press);
}

.button:hover {
  background: var(--color-interactive-primary-hover);
  box-shadow: var(--shadow-elevation-medium);
}

.button:active {
  background: var(--color-interactive-primary-active);
  transform: scale(0.98);
}

.button:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.button:disabled {
  background: var(--color-interactive-primary-disabled);
  color: var(--color-text-disabled);
  cursor: not-allowed;
  box-shadow: none;
}

/* Size variants */
.button-sm {
  padding: var(--spacing-stack-xs) var(--spacing-inline-sm);
  font: var(--typography-label-sm);
}

.button-lg {
  padding: var(--spacing-stack-md) var(--spacing-inline-lg);
  font: var(--typography-label-lg);
}

/* Variant: secondary */
.button-secondary {
  background: var(--color-interactive-secondary);
  color: var(--color-interactive-primary);
  border: var(--border-width-thin) solid var(--color-border-default);
}

.button-secondary:hover {
  background: var(--color-interactive-secondary-hover);
}

Every value comes from a token. The component adapts automatically when tokens change—whether that's for theming, rebranding, or design system updates.

Conclusion

CSS custom properties are the most straightforward way to implement design tokens for web projects. They work in every framework, require no build step, support runtime theming, and integrate with JavaScript.

To make them work well:

  1. Organize tokens by category (color, spacing, typography, etc.)
  2. Separate primitives from application tokens
  3. Use semantic naming (describe purpose, not appearance)
  4. Provide fallback values for resilience
  5. Generate TypeScript types for safety
  6. Theme with data attributes for runtime switching

When you structure tokens properly, your design system becomes flexible, maintainable, and AI-friendly. Tools like FramingUI extend this foundation by adding structure, validation, and programmatic access—making it even easier to manage tokens at scale.

Start with the structure shown here, adapt it to your needs, and build from there. Your future self 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