< Go back

Mastering Design Tokens at Scale: Multi-Brand, Multi-Theme Systems

12/6/2026 · 15 min read

Most teams get design tokens working. Very few get them working at scale. There's a significant difference between a token system that handles one product and one that powers five brands, three platforms, and a dozen theme variants without falling apart.

This article is the sequel to my earlier introduction to design tokens. If you're already comfortable with the basics — primitives, semantics, component tokens — this is where things get genuinely complex. We're talking about multi-tier token architecture, brand aliasing, theme switching at runtime, and the tooling decisions that make or break a large-scale system.

Why Single-Brand Token Systems Break at Scale

The classic token setup — a flat JSON file with color, spacing and typography values — works perfectly for a single product with a single theme. The moment you add a second brand or a white-label requirement, the cracks appear. You either duplicate everything (which defeats the purpose) or you try to parametrise values that weren't designed to be parametric.

  • Naming collisions: Brand A's color-primary is #FF3639. Brand B's color-primary is #3358ED. If tokens live in a single namespace, one overwrites the other.
  • Theme inheritance breaks: Dark mode tokens that reference light mode tokens directly (instead of through aliases) require duplication when you add a third theme.
  • Platform drift: iOS, Android and web each consume tokens differently. A system not designed for platform abstraction ends up with three diverging token files nobody trusts.
  • Governance collapse: When the system grows without a hierarchy, any designer can change any token at any time. There's no concept of "this token is global, don't touch it without a cross-team discussion".

The Three-Tier Token Architecture

The most robust pattern for large-scale systems uses three distinct tiers. Each tier has a clear purpose and a clear rule about who can reference it.

Tier 1: Primitive Tokens (Global)

These are your raw values. No semantics, no context. Just the complete palette of everything your system can express.

{
  "primitive": {
    "color": {
      "red-100": { "value": "#FFF0F0" },
      "red-500": { "value": "#FF3639" },
      "red-900": { "value": "#5C0002" },
      "blue-500": { "value": "#3358ED" },
      "neutral-0":   { "value": "#FFFFFF" },
      "neutral-1000": { "value": "#000000" }
    },
    "spacing": {
      "4":  { "value": "4px" },
      "8":  { "value": "8px" },
      "16": { "value": "16px" },
      "24": { "value": "24px" },
      "32": { "value": "32px" }
    }
  }
}

Rule: primitive tokens never reference other tokens. They only hold raw values. They are never used directly in components.

Tier 2: Semantic Tokens (Brand-level)

Semantic tokens assign meaning. They reference primitives by alias and describe intent rather than appearance.

/* Brand A */
{
  "semantic": {
    "color": {
      "brand-primary":     { "value": "{primitive.color.red-500}" },
      "brand-primary-subtle": { "value": "{primitive.color.red-100}" },
      "surface-default":   { "value": "{primitive.color.neutral-0}" },
      "surface-inverse":   { "value": "{primitive.color.neutral-1000}" },
      "text-primary":      { "value": "{primitive.color.neutral-1000}" }
    }
  }
}

/* Brand B — swap just this file */
{
  "semantic": {
    "color": {
      "brand-primary":     { "value": "{primitive.color.blue-500}" },
      "brand-primary-subtle": { "value": "{primitive.color.blue-100}" },
      "surface-default":   { "value": "{primitive.color.neutral-0}" },
      "surface-inverse":   { "value": "{primitive.color.neutral-1000}" },
      "text-primary":      { "value": "{primitive.color.neutral-1000}" }
    }
  }
}
Tier 3: Component Tokens

Component tokens map semantic tokens to specific UI elements. They're the last mile of the system.

{
  "component": {
    "button": {
      "background-default":  { "value": "{semantic.color.brand-primary}" },
      "background-hover":    { "value": "{semantic.color.brand-primary-subtle}" },
      "text":                { "value": "{semantic.color.surface-default}" },
      "border-radius":       { "value": "{primitive.spacing.8}" }
    }
  }
}

The power: swapping Brand A for Brand B only requires replacing the semantic tier file. The primitive tier and component tier stay identical.

Multi-Theme: Dark Mode Without Duplication

The common mistake is creating a separate dark mode token file that duplicates all component and semantic tokens. Instead, dark mode should only swap values at the semantic tier.

/* light.json — default semantic layer */
{
  "semantic": {
    "color": {
      "surface-default": { "value": "{primitive.color.neutral-0}" },
      "surface-raised":  { "value": "{primitive.color.neutral-50}" },
      "text-primary":    { "value": "{primitive.color.neutral-1000}" },
      "text-secondary":  { "value": "{primitive.color.neutral-600}" }
    }
  }
}

/* dark.json — only overrides what changes */
{
  "semantic": {
    "color": {
      "surface-default": { "value": "{primitive.color.neutral-1000}" },
      "surface-raised":  { "value": "{primitive.color.neutral-900}" },
      "text-primary":    { "value": "{primitive.color.neutral-0}" },
      "text-secondary":  { "value": "{primitive.color.neutral-400}" }
    }
  }
}

Notice: brand-primary doesn't appear in the dark theme file. It doesn't need to — the brand colour stays consistent across themes. Only surface and text roles switch.

Runtime Theme Switching with CSS Custom Properties

The cleanest way to implement this in production is to export all semantic tokens as CSS custom properties scoped to a theme attribute:

:root,
[data-theme="light"] {
  --color-surface-default: #FFFFFF;
  --color-surface-raised: #F5F5F5;
  --color-text-primary: #000000;
  --color-brand-primary: #FF3639;
}

[data-theme="dark"] {
  --color-surface-default: #000000;
  --color-surface-raised: #111111;
  --color-text-primary: #FFFFFF;
  /* brand-primary intentionally omitted — inherits from :root */
}

/* Theme switch: just one attribute */
document.documentElement.setAttribute('data-theme', 'dark');

Tooling: Style Dictionary at Scale

Style Dictionary is the de-facto standard for transforming token JSON into platform-specific outputs. At scale, the configuration becomes as important as the tokens themselves.

// style-dictionary.config.js
module.exports = {
  source: [
    'tokens/primitive/**/*.json',
    'tokens/semantic/brand-a/**/*.json',  // swap for brand-b
    'tokens/semantic/light/**/*.json',     // swap for dark
    'tokens/component/**/*.json'
  ],
  platforms: {
    css: {
      transformGroup: 'css',
      prefix: 'sd',
      buildPath: 'dist/css/',
      files: [
        {
          destination: 'variables.css',
          format: 'css/variables',
          options: { outputReferences: true }
        }
      ]
    },
    ios: {
      transformGroup: 'ios-swift',
      buildPath: 'dist/ios/',
      files: [{ destination: 'StyleDictionary.swift', format: 'ios-swift/class.swift' }]
    },
    android: {
      transformGroup: 'android',
      buildPath: 'dist/android/',
      files: [{ destination: 'tokens.xml', format: 'android/resources' }]
    }
  }
};

The key is the source array. Your CI/CD pipeline passes the brand and theme as environment variables, and your config selects the right token files to compile. Every platform gets one build command, consistent output, zero manual merging.

Automating Multi-Brand Builds
// build-tokens.js
const brands = ['brand-a', 'brand-b', 'brand-c'];
const themes = ['light', 'dark'];

brands.forEach(brand => {
  themes.forEach(theme => {
    const config = buildConfig({ brand, theme });
    StyleDictionary.extend(config).buildAllPlatforms();
    console.log(`Built ${brand}/${theme}`);
  });
});

Governance: Who Owns What

Technical architecture without governance collapses. At scale, you need explicit rules about token ownership.

  • Primitive tokens are owned by the core design system team. Changes require a design system review. These are the most stable tokens — they shouldn't change often.
  • Semantic tokens are owned jointly by design system and brand teams. A brand can add tokens in its namespace; it cannot modify shared semantic tokens without a cross-team sign-off.
  • Component tokens can be owned by individual product teams, within the constraint that they must reference semantic tokens — never primitives directly.
  • Version your token packages. Treat the token system as an npm package. Breaking changes (renaming or removing a token) get a major version bump. Additions are minor. Fixes are patches. Product teams pin to a version and upgrade on their own schedule.
Deprecation Without Breaking Things
/* Mark deprecated tokens with a comment and a redirect */
{
  "semantic": {
    "color": {
      "cta-color": {
        "value": "{semantic.color.brand-primary}",
        "deprecated": true,
        "comment": "Use brand-primary instead. Removed in v4.0."
      }
    }
  }
}

Style Dictionary can be configured to emit console warnings for deprecated tokens during build, giving consuming teams time to migrate before removal.

Syncing with Figma Variables

Figma's Variables feature maps almost perfectly onto the three-tier architecture. Collections in Figma correspond to tiers; modes within a collection correspond to themes and brands.

  • Create a Primitives collection with no modes — just the full palette of raw values.
  • Create a Semantic collection with modes: Light, Dark. Each mode maps primitives to roles.
  • Create a Brand collection with modes: Brand A, Brand B, Brand C. Each mode overrides the brand-specific semantic values.
  • Use the Tokens Studio plugin or Figma's REST API to export Variables to your Style Dictionary JSON format, keeping design and code in sync without manual copy-paste.

The most important discipline: every value a designer sets in Figma must exist as a token. No hardcoded hex values, no "I'll just eyedrop that". If it's not in the token system, it doesn't go into the product.

Conclusion

Multi-brand, multi-theme token systems aren't complicated — they're disciplined. The three-tier architecture (primitive → semantic → component), automated multi-brand builds via Style Dictionary, CSS custom property theming, and clear governance rules are the foundation of any token system that survives contact with real product growth. The teams that invest in this architecture spend less time in "why does the button look different on iOS" conversations and more time shipping.
Next article

Mastering Figma Variables: The Complete 2026 Playbook →

Would you like to collaborate?

Contact me