Skip to content

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:

typescript
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

TierCan OverrideCannot OverrideMechanism
T0 (Platform)Everything -- defines defaults--Published theme CSS files
T1 (Partner)semantic.* paths, seed colors, typography, harmony modeProtected tokens, spacing grid, radius system<style> block injected after theme import
T2 (Customer)Paths listed in meta.allowedTenantOverridesProtected tokens, anything T1 did not expose[data-tenant="<id>"] custom properties

Token Resolution Order

T0 defaults < T1 brand < cultural overlay < T2 overrides < user preferences

Last wins, except protected tokens which T0 locks regardless of what comes after.

In CSS terms:

css
/* 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:

typescript
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:

typescript
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:

  1. Admin browses identities with live preview
  2. Admin picks one (e.g., nihon-traditional)
  3. The selection sets T1 tokens for that partner's entire scope
  4. T2 customers under that partner inherit the identity
  5. 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 it

Widget 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

Released under the MIT License.