Multi-Brand Theming
Modern design systems serve multiple products, brands, and platforms. The two-tier token architecture makes this possible without duplicating the entire system.
The Theming Model
Multi-brand theming works through three layers. Core tokens define raw primitives (palettes, scales). Semantic tokens give those primitives meaning (accent, background, border). Brand tokens remap semantic values to different core primitives.
Semantic Token Default (red) Corporate (blue)
───────────────── ───────────── ────────────────
--semantic-color-foreground-accent → palette.red.500 → palette.blue.500
--semantic-color-background-accent → palette.red.500 → palette.blue.500
--semantic-color-border-accent → palette.red.500 → palette.blue.500
--semantic-color-foreground-link → palette.red.500 → palette.blue.600
--semantic-shape-control-radius → shape.radius.04 → shape.radius.02
--semantic-spacing-component-padding → spacing.size.05 → spacing.size.04
--semantic-motion-interaction-dur. → motion.dur.short → motion.dur.short2
Components always reference --semantic-* tokens.
Brand files remap which core primitives those tokens resolve to.The source files mirror this structure. Core token files define the shared primitives, and each brand file selectively overrides the semantic mappings:
Token Architecture
──────────────────────────────────────────────────────────────────
core/ semantic/ brands/
├── color.tokens.json ├── color.tokens.json ├── default.tokens.json
├── spacing.tokens.json ├── spacing.tokens.json ├── corporate.tokens.json
├── shape.tokens.json ├── shape.tokens.json ├── forest.tokens.json
├── motion.tokens.json ├── motion.tokens.json ├── sunset.tokens.json
├── typography.tokens.json└── typography.tokens.json├── midnight.tokens.json
├── elevation.tokens.json ├── ocean.tokens.json
└── density.tokens.json ├── canary.tokens.json
├── monochrome.tokens.json
density/ ├── rose.tokens.json
├── tight.tokens.json └── slate.tokens.json
├── compact.tokens.json
├── default.tokens.json
└── spacious.tokens.json
Components reference semantic tokens. Brand and density layers override
semantic values when [data-brand] and [data-density] attributes are set.What Changes Per Brand
Each brand can customize six categories of tokens:
- Color — Primary accent, link colors, highlights, action states
- Shape — Border radius (none, sharp, medium, rounded, pill), card borders, elevation
- Spacing — Component padding, gaps, and card spacing
- Motion — Transition duration and easing curves
- Typography — Font weights for body and headings
- Density — Spacing scale multipliers (tight, compact, default, spacious)
Brand Token Structure
Each brand token file references core primitives using the DTCG token reference syntax. The $extensions object provides separate light and dark values for color tokens, enabling per-brand dark mode adjustments:
// brands/corporate.tokens.json
{
"$brand": {
"name": "corporate",
"description": "Professional corporate brand with blue accents",
"accent": "blue",
"density": "compact"
},
"color": {
"foreground": {
"accent": {
"$type": "color",
"$value": "{color.palette.blue.500}",
"$extensions": {
"design.paths.light": "{color.palette.blue.500}",
"design.paths.dark": "{color.palette.blue.400}"
}
}
},
"background": {
"accent": {
"$type": "color",
"$value": "{color.palette.blue.500}",
"$extensions": {
"design.paths.light": "{color.palette.blue.500}",
"design.paths.dark": "{color.palette.blue.400}"
}
}
}
},
"shape": {
"control": {
"radius": {
"default": { "$value": "{shape.radius.01}" } // 2px - sharp
}
},
"card": {
"radius": { "$value": "{shape.radius.01}" },
"elevation": {
"default": { "$value": "{elevation.level.1}" }
}
}
},
"spacing": {
"component": {
"padding": { "$value": "{spacing.size.04}" }, // 8px - compact
"gap": { "$value": "{spacing.size.03}" } // 4px
},
"card": {
"padding": { "$value": "{spacing.size.05}" } // 12px
}
},
"motion": {
"interaction": {
"duration": { "$value": "{motion.duration.short2}" } // 83ms
}
},
"typography": {
"body": { "fontWeight": { "$value": "{typography.weight.medium}" } },
"heading": { "fontWeight": { "$value": "{typography.weight.bold}" } }
}
}CSS Output with Cascade Layers
The build system generates CSS with cascade layers. Each layer has increasing precedence—brand overrides always win over theme defaults, and theme defaults win over semantic defaults:
@layer core, semantic, theme, brand, density;
@layer theme {
/* Light/dark theme defaults (applied via class on <html>) */
.light {
--semantic-color-foreground-accent: #d9292b; /* default red */
--semantic-color-background-primary: #ffffff;
/* ...all semantic color tokens for light mode... */
}
.dark { /* dark mode overrides */ }
@media (prefers-color-scheme: dark) {
:root { /* system dark mode */ }
.light { /* manual light override when system prefers dark */ }
}
}
@layer brand {
/* Base brand overrides (light mode default) */
[data-brand="corporate"] {
--semantic-color-foreground-accent: var(--core-color-palette-blue-500);
--semantic-color-background-accent: var(--core-color-palette-blue-500);
--semantic-shape-control-radius-default: var(--core-shape-radius-01);
--semantic-shape-card-radius: var(--core-shape-radius-01);
--semantic-spacing-component-padding: var(--core-spacing-size-04);
--semantic-motion-interaction-duration: var(--core-motion-duration-short2);
--semantic-typography-body-font-weight: var(--core-typography-weight-medium);
}
/* Dark mode overrides for this brand */
@media (prefers-color-scheme: dark) {
[data-brand="corporate"] {
--semantic-color-foreground-accent: var(--core-color-palette-blue-400);
}
}
}
@layer density {
[data-density="compact"] {
--semantic-spacing-padding-container: var(--semantic-spacing-density-compact-lg);
--semantic-spacing-padding-card: var(--semantic-spacing-density-compact-sm);
--semantic-spacing-gap-grid: var(--semantic-spacing-density-compact-sm);
}
}Runtime Brand Switching
Brand and density switching at runtime is handled by updating data attributes on the document element. The BrandContext manages state and persistence:
// BrandContext manages brand, density, and font preferences
type BrandId = 'default' | 'corporate' | 'forest' | 'sunset' |
'midnight' | 'ocean' | 'canary' | 'monochrome' |
'rose' | 'slate';
type DensityId = 'tight' | 'compact' | 'default' | 'spacious';
function setBrand(brand: BrandId) {
document.documentElement.setAttribute('data-brand', brand);
localStorage.setItem('brand', brand);
}
function setDensity(density: DensityId) {
document.documentElement.setAttribute('data-density', density);
localStorage.setItem('density', density);
}
// Usage - instantly switches all tokens
setBrand('corporate');
setDensity('compact');Core Layer: Brand-Agnostic Primitives
The core layer contains primitives that all brands share. These are the raw materials—color palettes, spacing scales, typography ramps—that brands draw from. No brand defines its own palette; each one references values from this shared set.
// core/color.tokens.json - Universal palette
{
"palette": {
"red": { "500": { "$type": "color", "$value": "#d9292b" } },
"blue": { "500": { "$type": "color", "$value": "#0a65fe" } },
"green": { "500": { "$type": "color", "$value": "#22c55e" } },
"teal": { "500": { "$type": "color", "$value": "#14b8a6" } },
"purple": { "500": { "$type": "color", "$value": "#a855f7" } },
"yellow": { "500": { "$type": "color", "$value": "#eab308" } },
"neutral": { "500": { "$type": "color", "$value": "#737373" } }
}
}
// core/shape.tokens.json - Radius scale
{
"radius": {
"none": { "$value": "0px" }, // No radius
"01": { "$value": "2px" }, // Sharp
"02": { "$value": "4px" }, // Small
"medium": { "$value": "6px" }, // Medium-small
"03": { "$value": "8px" }, // Medium
"04": { "$value": "16px" }, // Large
"05": { "$value": "32px" }, // Extra large
"full": { "$value": "9999px" } // Pill
}
}
// core/density.tokens.json - Spacing density scales
{
"density": {
"tight": { "sm": "4px", "md": "6px", "lg": "8px" },
"compact": { "sm": "8px", "md": "12px", "lg": "16px" },
"default": { "sm": "12px", "md": "16px", "lg": "24px" },
"spacious": { "sm": "16px", "md": "24px", "lg": "32px" }
}
}Pitfalls to Avoid
1. Duplicating Core Tokens Per Brand
Each brand should reference core tokens, not define their own values. Duplicating defeats the purpose of the two-tier model.
// BAD: Brand defines its own primitives
{ "blue": { "500": { "$value": "#0a65fe" } } } // Duplicates core!
// GOOD: Brand references core primitives
{ "background": { "brand": { "$value": "{core.color.palette.blue.500}" } } }2. Inconsistent Semantic Structure
All brands must use the same semantic token structure. Components reference semantic paths, so those paths must exist in every brand.
3. Hardcoding Brand Logic in Components
Components should be brand-agnostic. They reference semantic tokens and let the token system handle brand differences.
// BAD: Brand logic in component
.button { background: var(--brand-a-blue); }
// GOOD: Brand-agnostic component
.button { background: var(--semantic-color-background-accent); }4. Forgetting Accessibility Across Brands
Each brand's semantic tokens must maintain accessibility requirements. A brand can't choose colors that fail contrast ratios.
Summary
- Core tokens are brand-agnostic — Shared palettes, scales, and ramps
- Semantic tokens are the theming surface — Where brands diverge
- Six dimensions of variation — Color, shape, spacing, motion, typography, density
- CSS cascade layers — Ensure brand and density overrides take precedence
- Runtime switching via data attributes — Instant brand and density changes
- Ten brand themes — Each with distinct color, shape, and motion personalities
Next Steps
Multi-brand theming relies on the resolver module for complex resolution scenarios. For simpler setups, understand how build outputs generate brand-specific artifacts, and how accessibility tokens ensure all brands remain inclusive.
Style
Dare to stand out
Striking patterns, vibrant colors, and statement pieces that speak to the bold fashion enthusiast. From abstract prints to architectural silhouettes, our collection celebrates those who dare to express their authentic style.
Back to basics
Simple and versatile
Pants and jeans
Jeans #7
$149
Jeans with a sense of nostalgia, as if they carry whispered tales of past adventures.
Footwear
Sneakers #12
$149
Love at the first sight for enthusiasts seeking a fresh and whimsical style.
Search products
Your team
Invite and manage your team members.
New order received
Order #1006 for $234.50
Sign up
Your company card
View and manage your corporate card.
John Doe
Today's metrics
Financial performance
Review your company's KPIs compared to the month before.
Invoice #3463
742 Evergreen Terrace
Springfield, IL 62074
1600 Baker Street NW
Washington, DC 20500
Invoice paid
You paid $17,975.30. A receipt copy was sent to [email protected].
Recent activity
Order #1005 shipped
2 minutes ago
New customer registered
15 minutes ago
Payment received: $234.50
1 hour ago
Conversion rate updated
2 hours ago
Preferences
Notifications
Manage your notification settings.
To-do
Stay on top of your daily tasks.
Top customers
Your profile
Privacy
Danger zone
Dashboard overview
View your key metrics and recent activity at a glance.
Project progress
Low inventory warning
5 products are running low on stock. Consider restocking soon.
Pricing
No credit card required. Every plan includes a 30-day trial of all Pro features.
- Expense tracking
- Invoicing
- Basic reports
- Online payments
- Bill management
- Detailed reports
- Custom invoices
- Multi-business
- Priority support
Edit product
Size
Material
Color
Orders
May 2023| Order no. | Payment | Fulfillment | Amount |
|---|---|---|---|
| #1005 | Paid | Delivering | $154.60 |
| #1004 | Paid | Unfulfilled | $93.49 |
| #1003 | Refunded | Cancelled | $39.00 |
| #1002 | Unpaid | Unfulfilled | $438.90 |
| #1001 | Paid | Fulfilled | $532.64 |
| #1000 | Paid | Fulfilled | $625.03 |
Shipment tracking
Delivery
Tomorrow
12:00 pm – 2:00 pm
Luna Rodriguez
9876 Maple Avenue
Cityville, WA 54321
Shopping cart
Poncho #4
Size M
$79
Jeans #8
Size 30
$118
Sneakers #14
Size 8
$116
Bookmarks
Jeans #8
$118
Jacket #3
$49
Pants #10
$32
Shirt #11
$39