WritingSemantic Design Tokens: Architecting Scalable Theming with CSS Variables — Clixo
5 min readdesign tokens, css variables, theming, design systems

Semantic Design Tokens: Architecting Scalable Theming with CSS Variables

Deep dive into semantic design tokens and CSS variables for scalable theming: token tiers, dark mode, multi-brand support, and the pitfalls of flat token architectures.

You shipped dark mode in a weekend — by wrapping the entire app in a CSS class and overriding a hundred color values one by one. It works, mostly. But now a second brand is on the roadmap, and the question of how you handle that without duplicating the entire theme is one you do not have a clean answer to. The architecture you need is a semantic token layer backed by CSS custom properties.

The Problem with Flat Token Systems

A flat token system maps names directly to values: --color-blue: #1A73E8. Engineers use --color-blue wherever they need that color. This is better than hardcoding hex values, but only marginally. When you need a dark theme, you are still swapping raw color values. When you need a second brand, you are rewriting every reference. The semantic layer is what transforms token systems from a color list into a theming architecture.

Semantic Design Tokens: The Core Concept

Semantic tokens express intent, not value. Instead of --color-blue, you have --color-action-primary. The name communicates the role. The value can change — to a different blue, to a green, to whatever the dark variant requires — without changing any component code that references the token.

The architecture works in two tiers:

Primitive tokens hold raw values and live at the :root level. They are your complete palette.

:root {
  --palette-blue-500: #1a73e8;
  --palette-blue-600: #1557b0;
  --palette-neutral-50: #f8f9fa;
  --palette-neutral-900: #1a1a1a;
}

Semantic tokens reference primitives and express roles.

:root {
  --color-action-primary: var(--palette-blue-500);
  --color-action-primary-hover: var(--palette-blue-600);
  --color-surface-default: var(--palette-neutral-50);
  --color-text-default: var(--palette-neutral-900);
}

Components consume only semantic tokens. A button's background uses var(--color-action-primary), never var(--palette-blue-500).

How Theming Works at the Semantic Layer

Dark mode does not require touching a single component. You redefine semantic tokens within a theme selector:

[data-theme="dark"] {
  --color-surface-default: var(--palette-neutral-900);
  --color-text-default: var(--palette-neutral-50);
  --color-action-primary: var(--palette-blue-400);
}

The button still reads var(--color-action-primary). The value it resolves to has changed. No component code changed.

This pattern extends cleanly to system preferences:

@media (prefers-color-scheme: dark) {
  :root {
    --color-surface-default: var(--palette-neutral-900);
    --color-text-default: var(--palette-neutral-50);
  }
}

Multi-Brand Theming with Semantic Tokens

Multi-brand theming follows the same pattern. Each brand defines its own primitive palette and remaps semantic tokens to those primitives. Component code is unchanged.

/* Brand A */
[data-brand="alpha"] {
  --palette-brand-primary: #0057b8;
  --palette-brand-secondary: #ff6b00;
  --color-action-primary: var(--palette-brand-primary);
}
 
/* Brand B */
[data-brand="beta"] {
  --palette-brand-primary: #28a745;
  --palette-brand-secondary: #6c757d;
  --color-action-primary: var(--palette-brand-primary);
}

Switch the data-brand attribute at the root and the entire product re-skins. This is the architecture that makes white-labeling manageable rather than a full re-engineering effort.

Organizing Semantic Tokens at Scale

As the token system grows, organization matters. Group semantic tokens by category, not by component:

  • Surface tokens — backgrounds, cards, overlays
  • Text tokens — default, secondary, disabled, on-brand
  • Action tokens — primary, secondary, destructive, ghost
  • Feedback tokens — error, warning, success, info
  • Border tokens — default, focus, error
  • Elevation tokens — shadow scales

Avoid creating a new semantic token for every component. --button-background-primary and --input-border-color seem specific, but they usually duplicate what --color-action-primary and --color-border-default already express. Add component-level tokens only when a component genuinely diverges from semantic defaults in ways that need to be overrideable.

Token Generation and Build Tooling

Manually maintaining CSS custom properties alongside JSON or YAML token sources is error-prone. Use a build tool to generate platform outputs from a single source.

Style Dictionary is the most widely adopted option. You define tokens once in JSON:

{
  "color": {
    "action": {
      "primary": { "value": "{palette.blue.500}" }
    }
  }
}

Style Dictionary outputs CSS custom properties, JavaScript objects, Swift color definitions, or Android XML from the same source. The token source is version-controlled and becomes the single source of truth that Figma variables, web CSS, and native platforms all derive from.

The Cascade Is Your Friend

One advantage of CSS custom properties over preprocessor variables (Sass, Less) is runtime mutability and cascade support. You can override a token at any level of the DOM tree:

.high-contrast-region {
  --color-text-default: #000000;
  --color-surface-default: #ffffff;
}

This enables granular accessibility accommodations — a high-contrast section within a standard-contrast page — without a separate theme selector at the root.

Common Architectural Mistakes

Using primitives in component code. The moment an engineer writes var(--palette-blue-500) in a component file, that component is invisible to the theming system.

Semantic tokens that reference other semantic tokens. Chains of variable references add complexity without benefit. Semantic tokens should resolve to primitives, not to other semantic tokens.

Creating semantic tokens before knowing their consumer. Tokens defined speculatively without a component consuming them tend to accumulate and fragment the namespace. Build tokens in response to real component needs.

Neglecting contrast audits at the semantic layer. Document which semantic token combinations meet WCAG contrast thresholds, and recheck after every theme change. A dark theme that passes in design often fails in contrast testing.

What This Architecture Enables

A semantic token system backed by CSS custom properties is what makes all the expensive operations cheap: dark mode becomes a configuration file, rebranding becomes a token remap, and accessibility themes become overrides. The component library stays stable as the visual layer evolves around it.

Clixo architects token systems and theming layers for product teams building across multiple surfaces and brands.