Token Governance
Design tokens flow through three tiers. Each tier can override the one above it, with one exception: protected tokens are locked at Tier 0 and cannot be changed by anyone below.
Why Protected Tokens Exist
Status colors and focus indicators are accessibility requirements, not brand choices. If a T1 partner overrides status.error to match their brand, colorblind users lose the ability to distinguish errors from success. If a T2 customer removes the focus ring, keyboard users cannot navigate.
Protected tokens exist so tenant customization cannot break usability.
Protected Token List
These paths are locked at T0. No tier below can override them:
export const PROTECTED_TOKEN_PATHS = [
'semantic.light.status.error',
'semantic.light.status.success',
'semantic.light.status.warning',
'semantic.dark.status.error',
'semantic.dark.status.success',
'semantic.dark.status.warning',
'semantic.light.focus.ring',
'semantic.dark.focus.ring',
'semantic.light.accessibility',
'semantic.dark.accessibility',
] as const;This list is in packages/tokens/src/constants.ts. The build pipeline enforces it -- attempts to override these paths are rejected at generation time, not at runtime.
Tier Override Rules
| Tier | Can Override | Cannot Override | Mechanism |
|---|---|---|---|
| T0 (Platform) | Everything -- defines defaults | -- | Published theme CSS files |
| T1 (Partner) | semantic.* paths, seed colors, typography, harmony mode | Protected tokens, spacing grid, radius system | <style> block injected after theme import |
| T2 (Customer) | Paths listed in meta.allowedTenantOverrides | Protected tokens, anything T1 did not expose | [data-tenant="<id>"] custom properties |
Token Resolution Order
T0 defaults < T1 brand < cultural overlay < T2 overrides < user preferencesLast wins, except protected tokens which T0 locks regardless of what comes after.
In CSS terms:
/* T0: Base theme (loaded first) */
@import "@syncupsuite/themes/swiss-international/tokens.css";
/* T1: Partner brand override */
:root {
--interactive-primary: #1a56db; /* Allowed -- not protected */
--text-primary: #1f2937; /* Allowed */
/* --status-error: #ff0000; REJECTED -- protected */
}
/* T2: Customer fine-tuning */
[data-tenant="customer-uuid"] {
--background-canvas: #fefefe; /* Allowed if T1 exposed it */
}WCAG Contrast Validation
The build pipeline validates 20 foreground/background contrast pairs at generation time:
export const CONTRAST_PAIRS: ContrastPair[] = [
{ fg: 'semantic.light.text.primary',
bg: 'semantic.light.background.canvas', minRatio: 4.5 },
{ fg: 'semantic.light.text.secondary',
bg: 'semantic.light.background.canvas', minRatio: 4.5 },
{ fg: 'semantic.light.text.muted',
bg: 'semantic.light.background.canvas', minRatio: 3 },
{ fg: 'semantic.light.interactive.primary',
bg: 'semantic.light.background.canvas', minRatio: 3 },
{ fg: 'semantic.light.status.error',
bg: 'semantic.light.background.canvas', minRatio: 3 },
// + dark mode mirrors of all 10 pairs above
];Every theme gets these checks. If a contrast pair fails, the build reports a warning. Some cultural palettes (De Stijl, Wiener Werkstatte) have inherent contrast limitations -- these warnings are non-blocking but documented.
Performance Budgets
Token output is also size-constrained:
export const PERF_BUDGETS = {
maxCssGzipped: 20_480, // 20KB max per theme CSS, gzipped
maxProperties: 500, // Max CSS custom properties per theme
maxDepth: 8, // Max nesting depth in DTCG JSON
};These budgets prevent token bloat as themes grow. A theme that exceeds any budget fails the build.
How BrandSyncUp Uses This
BrandSyncUp's admin interface includes a BrandIdentityPicker component where partners select from the 12 curated cultural identities. The selection flow:
- Admin browses identities with live preview
- Admin picks one (e.g.,
nihon-traditional) - The selection sets T1 tokens for that partner's entire scope
- T2 customers under that partner inherit the identity
- T2 customers can override within
meta.allowedTenantOverrides
The picker lives in src/components/brand/BrandIdentityPicker.tsx. It loads theme CSS dynamically and applies it to a preview container so the admin sees the result before committing.
Governance in Practice
A concrete example. Partner "Acme Corp" selects Swiss International:
T0 (Platform):
--status-error: #DC2626 LOCKED (protected)
--focus-ring: #2563EB LOCKED (protected)
--interactive-primary: #111111 Default from Swiss International
T1 (Acme Corp):
--interactive-primary: #0052CC Overridden to Acme's brand blue
--text-primary: #172B4D Overridden to Acme's text color
T2 (Acme's customer "Widget Inc"):
--background-canvas: #FAFBFC Fine-tuned within Acme's allowed range
--interactive-primary: ??? Cannot override -- Acme didn't expose itWidget Inc sees Acme's brand blue, not the Swiss International default. They can tweak their canvas color but cannot change the brand color or any protected token.
Next Steps
- Multi-tenant architecture -- the tier model that token governance sits on
- Custom Foundations -- create your own cultural identity
- Semantic Colors -- the token API every theme provides