| name | brand-theming |
| description | Multi-brand token sets and white-label pipelines for mobile design systems. Use this when adding a new brand or building a themeable product. |
Brand Theming
Instructions
A brand is a token set, not a code fork. A clean brand pipeline lets you ship N brands from one codebase, one component library, and one CI job.
1. Brand = Token Overlay
Keep the system and component layers shared. Overlay only the reference and, rarely, system layers per brand.
tokens/
├── base/
│ ├── reference/
│ ├── system/
│ └── component/
└── brands/
├── acme/
│ ├── reference.override.json # brand hues, brand fonts
│ └── system.override.json # optional intent remaps
└── globex/
└── reference.override.json
Style Dictionary resolves base → brand so the brand overlay wins.
2. Minimum Brand Surface
A brand usually needs to control:
- Primary / secondary brand hue (tonal palettes regenerated via HCT).
- Font family (text composites unchanged).
- Logo and app icon assets.
- Radius personality (0 = sharp, 16 = friendly, pill = playful).
- Motion personality (snappy vs smooth easing defaults).
Do not let brands change spacing scale, breakpoints, or component APIs.
3. Brand Config Example
{
"brand": {
"name": { "$value": "Acme" },
"hue": {
"primary": { "$value": "#4F46E5" },
"secondary": { "$value": "#14B8A6" }
},
"font": {
"family": { "$value": ["Acme Sans", "Inter", "system-ui"] }
},
"radius": {
"personality": { "$value": "12px" }
}
}
}
The build regenerates color.brand.0..100 from hue.primary using the HCT palette generator.
4. Style Dictionary Multi-Brand Build
const StyleDictionary = require('style-dictionary');
const brands = ['acme', 'globex'];
for (const brand of brands) {
StyleDictionary.extend({
source: [
'tokens/base/**/*.json',
`tokens/brands/${brand}/**/*.json`,
],
platforms: {
android: { transformGroup: 'compose', buildPath: `build/${brand}/android/`, files: [] },
ios: { transformGroup: 'ios-swift', buildPath: `build/${brand}/ios/`, files: [] },
flutter: { transformGroup: 'flutter', buildPath: `build/${brand}/flutter/`,files: [] },
rn: { transformGroup: 'js', buildPath: `build/${brand}/rn/`, files: [] },
},
}).buildAllPlatforms();
}
5. App-Side Selection
Apps select a brand at build time (preferred) or at runtime (for white-label tenants).
Build-time (Android flavors):
flavorDimensions += "brand"
productFlavors {
create("acme") { dimension = "brand"; resValue("string", "brand", "acme") }
create("globex") { dimension = "brand"; resValue("string", "brand", "globex") }
}
Build-time (iOS Xcode config / xcconfig): separate schemes per brand.
Runtime (RN / Flutter white-label):
const BrandContext = createContext<BrandTheme>(acmeTheme);
export const BrandedApp = ({ brandId }: { brandId: string }) => {
const theme = useMemo(() => loadBrand(brandId), [brandId]);
return <BrandContext.Provider value={theme}><Root /></BrandContext.Provider>;
};
6. Assets Per Brand
- Logos, app icons, splash images, and marketing illustrations ship per brand under
assets/brands/<id>/.
- The build copies only the selected brand's assets into the platform bundle for build-time selection.
- Runtime selection must lazy-load to avoid shipping every tenant's assets in one binary.
7. Governance
- A new brand goes through a lint that verifies: contrast AA across all semantic tokens, font availability, icon size compliance, and logo aspect ratios.
- Brands cannot add new token categories — only override existing ones.
- The base team owns the schema; brand teams own their overlay.
8. Anti-Patterns
- Forking the component library per brand.
- Letting brands override component APIs or spacing scale.
- Runtime brand selection without lazy asset loading (ships bloat).
- Brand overlays that touch
system or component layers instead of reference.
- Silent contrast regressions because brand validation is manual.
Checklist