Case 01: One token set, seven brands
I designed the token architecture that lets one component library serve every white-labeled client brand, so a new brand is a theme, not a fork.
- Scale
- 7 brand themes (Zinnia and 6 carriers) on one set of variables
- New brand cost
- Most carriers change fewer than 40 of 731 variables
- Most customized
- 106 of 731 variables, for the heaviest carrier theme
| Core | Base | Component |
|---|---|---|
color-900-gray | surface-darkerBase · Surface | button-primary-default-fillButton · Primary |
color-white | text-lightBase · Text | button-primary-default-textButton · Primary |
color-secondary | selected-fill-altStates · Selected | toggle-selected-fillToggle |
| Token | Value |
|---|---|
color-primary | #FF7500 |
color-primary-lightest | #FFEAD9 |
color-primary-dark | #994600 |
color-secondary | #00628B |
color-secondary-lightest | #D9E7EE |
color-accent-one | #FFC600 |
color-900-gray | #212121 |
| Token | Points to |
|---|---|
surface-primary | color-white |
surface-darker | color-900-gray |
border-primary-color | color-primary |
border-secondary-color | color-secondary |
text-primary | color-900-grayT |
text-light | color-whiteT |
text-link | color-global-linkT |
Key decisions
- Three tiers, one directionCore colors feed base tokens, and base tokens feed component tokens. Type, spacing and radius follow the same pattern. Every component token references another token rather than a raw hex value.Trade-off: more tokens to name up front, in exchange for brand changes that live in the variables, not the components.
- Name by intent, not appearance
button-primary-default-fill, notcolor-900-gray. Names survive rebrands and read the same to designers, engineers and AI tools.Trade-off: a naming guide alone didn't stop drift without oversight. Renames are breaking changes, so each corrected version ships beside the old one until the old one is deprecated. - Keep the core layer out of reachBrand core colors and raw type and space values are hidden from Figma's pickers, so teams apply only base and component tokens. A carrier's palette changes in one place, and no screen hard-codes a brand color. Only an extended palette for charts and illustrations stays in reach.Trade-off: when a design needs a shade with no base token, the answer is a new base token rather than a quick pick.
Outcome
- Shipped
- A shared visual language, a tiered token architecture, component libraries for desktop and mobile web, and usage guidelines.
- Used by
- Multiple product teams and business units, across Zinnia, Security Benefit, Everly and four more carrier brands.
- What changed
- Brand work lives in the variables instead of the components, so teams build a feature once and every client gets a consistent version.
Read the full story: One token set, seven brands
Context
Zinnia's products run under many client brands. Shared product teams build each feature once, and it has to feel native to every client that ships it. The design system sits under every product team and business unit in the company.
Problem
Zinnia grew through acquisitions: SE2, life.io, Breathe Life and Convergent became one company in 2022, and Policygenius joined in 2023. That brought together products from several companies, with no white-labeled design system and no design tokens. Without a shared foundation, every new brand meant overriding styles by hand, and every override became drift that engineering had to maintain.
What I did
Bloom began as a component library and grew into the design system for the whole of Zinnia. I built the foundations first: typography, color and iconography as one visual language. On top of that I set up a tiered token architecture and built the component libraries so that components consume base tokens wherever a base role exists. The libraries cover two ranges. Zinnia Live is built for desktop widths from 500px to 1140px and up, and the consumer product supports phones down to 360px.
In numbers: 731 variables in 12 collections, 609 published to teams and 122 hidden primitives. 125 core colors feed 109 base tokens and 130 component tokens across 19 components, and 223 of those 239 carry a usage note. Type runs on 48 styles, spacing on a 4px scale, and layout on five breakpoints from 360px. Each carrier swaps 14 to 20 core colors and re-points only the base and component tokens its brand needs.
Today the code library ships a default theme plus six carrier themes, switched with one attribute, and adding a carrier is a scripted step.
One component's tokens: the primary button
| State | Fill | Text | Rendered |
|---|---|---|---|
| Default | surface-darker | text-light | |
| Hover | surface-primary | text-primary | |
| Pressed | surface-quaternary | text-primary | |
| Selected | surface-tertiary | text-primary |
button-primary-{state}-fill and -text, pointing to a base role. The border stays border-darker in every state.