01 / 14
Option D · 20-Brand White Label · ComeOn · 2026-04-22

Design Token
Architecture v4

Move brand modes down to Semantic where designers and devs actually bind. Flatten Primitives into a clean, unmoded palette library. Keep Theme, Regulatory, and Component exactly where they are. One mode axis per collection, no magic, no drift.

brand on semantic primitives flat 5 collections 9–20 brand modes 2 themes 7 jurisdictions ~990 flat primitives

→ Token Reference v4 → Compare with v3 (Option A) → Brand Audit

v4 is a proposal. v3.1 is the current shipped architecture — kept live on the landing page as Option A.

02 Why v4

v3.1 works. But modes live where nobody binds.

After v3.1 shipped, both devs and designers pushed back that brand modes sit on the wrong collection. They bind to Semantic. The brand switch is two collections away. The complaint is one sentence, from two angles:

Designer

"I bind a button to interactive/primary/bg — that's Semantic. I want to preview it on Snabbare. But the brand mode switch is on Primitives, a collection I don't normally touch. Why isn't it where I work?"

Developer

"I consume Semantic via Tailwind. My bundle is one flat CSS file. All nine brands run through it, so where's the brand variation? Nowhere — I'm hand-writing theme.css."

What v4 changes

  • Brand mode axis moves from Primitives to Semantic. Designers switch brand on the layer they bind to.
  • Primitives flatten into an unmoded palette: color/lyllo/primary-600, color/snabbare/primary-600, etc. One canonical location per hex.
  • Theme stays at 2 modes (light/dark). Regulatory stays at 7 (jurisdictions). Component stays unmoded.
  • Each mode axis lives in exactly one collection. No cross-collection mode-matching. No naming lint.
  • Dev export pipeline emits one CSS bundle per brand × theme, replacing the hand-written theme.css.
03 The 5-collection model

Same five collections. Modes redistributed.

Two collections change shape. Three stay identical to v3.1. The number of collections is unchanged.

1 · Primitives ★ flatten · no modes
brand-prefixed flat names · color/<brand>/<ramp> · ~990 tokens
2 · Theme 2 modes · light / dark
unchanged · ~28 neutral-flip tokens · surface · text · border · overlay
3 · Semantic ★ 9–20 brand modes · NEW
text.* · surface.* · icon.* · pill.* · badge.* · radius.* · interactive.* · ~340 role tokens, each aliases its mode's brand-prefixed primitive
4 · Regulatory 7 jurisdiction modes
unchanged · default · SE · DK · MT · ON · NL · PL · ~22 tokens
5 · Component no modes · locked
unchanged · ~35 alias packs · cta.* · header.* · menu-row.* · marketing-locked

★ = changed from v3.1. Brand is the mode axis on Semantic, not on Primitives. Primitives becomes a flat palette library. Every hex lives in exactly one place.

04 Diff · v3.1 → v4

What actually changes, in a table.

Five collections, two rows changed.

Collectionv3.1 (Option A · shipped)v4 (Option B · proposal)
1 · Primitives 20 brand modes · ~110 tokens each · ~2200 values ★ unmoded · flat names · ~990 tokens
2 · Theme 2 modes (light/dark) · ~28 tokens unchanged
3 · Semantic no modes · ~340 tokens, aliases Primitives + Theme ★ 9–20 brand modes · ~340 tokens per mode
4 · Regulatory 7 jurisdiction modes · ~22 tokens unchanged
5 · Component no modes · ~35 alias packs · locked unchanged

Where each mode axis lives

Axisv3.1v4
BrandPrimitivesSemantic
ThemeThemeTheme
JurisdictionRegulatoryRegulatory

The invariant v4 establishes

Each mode axis lives in exactly one collection. No cross-collection mode-matching. No "Primitives and Semantic both have a brand mode named Lyllo, please stay in sync." Just aliases that explicitly name their target.

05 How the 5 collections link

The alias chain, end to end.

Values flow right to left through the chain. A consumer binds to Component (or Semantic); Figma walks backward through the aliases to land on a hex in Primitives.

1 · Primitivesunmoded · flat hex
2 · Theme2 modes · light/dark
3 · Semantic★ brand modes
5 · Componentno modes · locked

Reading: A → B means B aliases from A. Regulatory (4) sits beside Semantic as a narrow override layer for compliance tokens only.

The one rule of Component

Component aliases Semantic only. Never reaches around to Primitives directly. Never touches Theme directly. That's why Component doesn't need modes of its own — all brand variation is resolved by the time the alias lands in Semantic.

Cross-collection rules

  • Primitives → nothing. Holds raw hex.
  • Theme → Primitives. Per light/dark.
  • Semantic → Primitives + Theme. Per brand.
  • Regulatory → Primitives + Semantic. Per jurisdiction.
  • Component → Semantic only.
06 Worked example

CTA background · Snabbare · dark.

One button. One hex answer. Traced through every collection in v4.

# Frame: <button> fill binds to a Component token

Component (no modes):
  cta/primary/bgSemantic.interactive/primary/bg

Semantic (mode = snabbare):
  interactive/primary/bgPrimitives.color/snabbare/primary-600

Primitives (unmoded):
  color/snabbare/primary-600   =   #1e8f3e   # Snabbare green

# Frame renders: #1e8f3e

Switch Semantic's mode to lyllo — the Component bind stays identical. Semantic's alias, per its lyllo mode, now points at color/lyllo/primary-600 = #ff6699. The button becomes Lyllo pink. No magic: each mode has an explicit alias target.

Why this trace matters

At no point does Figma have to "match modes across collections by name." Semantic's snabbare mode just contains an explicit alias: "point at color/snabbare/primary-600 in Primitives." Primitives is unmoded, so there's nothing to match — it returns the one hex it holds.

07 Designer workflow

Before / after · previewing a CTA across brands.

BEFORE · v3.1

  1. Drop a frame.
  2. Rectangle → fill binds to interactive/primary/bg (Semantic).
  3. To preview Snabbare: switch the Primitives collection's mode to snabbare.
  4. To preview dark: switch the Theme collection's mode to dark.
  5. ⚠ You're operating on a collection (Primitives) that you never bind to. Easy to forget. Easy to mis-click.

AFTER · v4

  1. Drop a frame.
  2. Rectangle → fill binds to cta/primary/bg (Component) or interactive/primary/bg (Semantic).
  3. To preview Snabbare: switch the Semantic collection's mode to snabbare.
  4. To preview dark: switch the Theme collection's mode to dark.
  5. ✓ Brand mode lives on the collection you bind to. Two switches. Both on layers you already work with.

What does not change for designers

  • The tokens you bind to keep their names (interactive/primary/bg, text/primary, cta/primary/bg, etc.).
  • Component is still locked from marketing.
  • Regulatory is still a separate switch for compliance flows.
  • Primitives remains available if you ever need to reference a raw hex — just unmoded and brand-prefixed.
08 Developer workflow

Before / after · shipping Snabbare Dark.

BEFORE · v3.1

  1. Open hand-written theme.css.
  2. Copy / adapt values for Snabbare's palette.
  3. Keep a parallel file per brand. Manually sync when Figma changes.
  4. ⚠ Drift is guaranteed over time. Token-to-CSS transcription is manual.

AFTER · v4 + pipeline

  1. Style Dictionary walks Semantic under each mode combination.
  2. Emits dist/tokens/<brand>-<theme>.css per combo — 18 bundles for 9 brands × 2 themes.
  3. App shell sets <html data-brand="snabbare" class="dark">.
  4. Tailwind v4 @theme block maps CSS vars to utility classes. Components keep using bg-interactive-primary.
  5. ✓ Updates propagate by rebuild, not transcription.
/* Auto-generated · dist/tokens/snabbare-dark.css */
[data-brand="snabbare"] {
  --color-interactive-primary-bg: #1e8f3e;
  --color-text-primary: #0a0a0a;
  /* ... */
}
[data-brand="snabbare"].dark {
  --color-interactive-primary-bg: #1e8f3e;
  --color-text-primary: #ffffff;
}
09 Component

Why Component exists (and stays unmoded).

Semantic is generic roles. Component is specific UI elements that can't be expressed by a generic role alone. Three real examples:

Snabbare's isolated header

Snabbare's header is not brand primary. Using interactive/primary/bg paints the wrong colour. Component pins it:

header/bgSemantic.chrome/header/bgPrimitives.color/snabbare/header-chrome
  = #0b5ed7

Menu-row icon split

Designers wanted action icons and chevrons themed independently. Semantic's generic icon/action couldn't split. Component does:

menu-row/action-iconSemantic.icon/action
menu-row/chevronSemantic.icon/chevron

CTA context variants

CTA radius differs by context: form / card / wallet / landing. One Semantic role can't carry four radius values. Component does:

cta/primary/bgSemantic.interactive/primary/bg
cta/form/radius     = 8
cta/wallet/radius   = 4
cta/landing/radius  = 999

Why Component needs no modes

Component tokens alias Semantic. When Semantic's brand mode changes, Semantic resolves to a different Primitive. Component's aliases to Semantic automatically resolve through that. Brand variation propagates through the chain without Component participating.

10 Migration

The shape of the work.

Four phases. Each one verifiable on its own. Global Search prototype at localhost:5180 is the validation surface throughout.

Phase 1 · Pre-work

  • Brand-collapse audit against brand-audit.html.
  • Identify brands ≥95% Lyllo-identical (candidates: reviant-A, reviant-B, 888.nl).
  • Outcome: ~12 "real" brands + sub-brands overriding 3–8 accent tokens each.
  • Cuts rename surface by ~40%.

Phase 2 · Flatten Primitives

  • Throwaway Figma branch from AcbohbUnh3brJw52d3YlY6.
  • Start with Snabbare (99.5% hex parity already). Rename moded → flat form.
  • Verify byte-for-byte hex parity per brand.
  • Delete the Brand mode axis on Primitives.

Phase 3 · Add Semantic brand modes

  • Add lyllo (default) + snabbare modes first.
  • For each Semantic token, bind each mode's alias to the correct brand-prefixed Primitive.
  • ~290 brand-identical tokens copy the same alias across modes. ~50 diverge.
  • Scale out; sub-brands inherit, overriding 3–8 aliases.

Phase 4 · Dev pipeline & cut-over

  • Wire Style Dictionary against Figma REST export.
  • Emit per-bundle CSS under dist/tokens/.
  • Swap Global Search Casino off hand-written theme.css.
  • Validate <html data-brand> + .dark at localhost:5180.
11 Trade-offs

What you gain. What you pay.

Upsides

  • Each axis lives in exactly one collection. Simple mental model.
  • No cross-collection mode-matching. No naming lint needed.
  • Primitives becomes a clean inspectable palette library.
  • Modes live where designers and devs bind — complaint resolved.
  • Explicit aliases · no magic Figma behaviour to rely on.
  • Dev CSS export is deterministic and per-brand.

Downsides accepted

  • Primitives library grows from ~110 moded tokens to ~990 flat ones. Longer list, but searchable.
  • Large one-time migration: every Primitive renamed.
  • Each Semantic brand mode has to explicitly bind ~340 aliases (much of it copy-paste across modes).
  • Lyllo-preserved constraint needs a byte-for-byte hex check during rename.
  • The "20 future brand slots" goes away — add brand tokens when you add a brand, not ahead of time.

Locked decisions

  • Regulatory stays separate (7 jurisdiction modes, 22 tokens). Independent of brand/theme.
  • Brand-collapse audit runs first, read-only, before any Figma rename.
  • Lyllo hex values preserved byte-for-byte.
  • Component collection stays. It's the locked overrides layer for UI-specific binds.
12 After v4 ships

Adding a new brand, a new theme, a new jurisdiction.

Because each axis owns exactly one collection, each kind of addition touches one place.

Add a new brand

  1. Add color/<newbrand>/* to Primitives (~110 new flat tokens).
  2. Add a new mode to Semantic; bind ~340 aliases (mostly copy-paste).
  3. Pipeline rebuilds; <newbrand>-light.css and <newbrand>-dark.css appear in dist/.

Add a sub-brand

  1. Add 3–8 color/<subbrand>/accent* to Primitives.
  2. Add one Semantic mode overriding only the 3–8 accent tokens. Inherit the rest.
  3. Pipeline rebuilds.

Add a third theme (e.g. high-contrast)

  1. Add a third mode to Theme (high-contrast).
  2. Bind the ~28 Theme tokens for that mode.
  3. Pipeline rebuilds; every brand × theme combo gets a new bundle.

Marketing permissions (unchanged)

Marketing can edit Primitives (raw hex) only. Semantic, Theme, Regulatory, Component stay locked to the design system team. Same permission matrix as v3.1.

13 Verification

How we know v4 actually works.

Every phase produces a checkable artefact. No "looks good in Figma" — everything lands in a prototype or a CSS bundle that can be diffed.

PhaseCheckPass condition
Snabbare flatten hex parity vs v3.1 export byte-for-byte identical
Two-brand Semantic CTA renders correctly on 4 combos
(lyllo/snabbare × light/dark)
matches visual regression baseline
Pipeline output Style Dictionary → snabbare-dark.css
diffed against hand-written theme.css
zero semantic differences · hex-equivalent
Prototype integration Global Search at localhost:5180
<html data-brand="snabbare"> + .dark
Snabbare green primary · correct dark chrome
Full system 9 brands × 2 themes = 18 bundles all pass visual regression
End of v4 proposal · 2026-04-22

Proposal for v4.

Modes where designers bind. Primitives flat where devs inspect. Theme, Regulatory, Component preserved. One axis per collection. No magic.

Token Reference v4 Compare · v3 (Option A) Reference v3.1 Brand Audit Home

Design System · ComeOn · v4 is a proposal, not shipped · based on Figma file AcbohbUnh3brJw52d3YlY6