Rebuilding our design system with semantic tokens
Rebuilt our colour foundation with tokens. Normalised ramps, mapped every component to semantics, and shipped light/dark with brand parity. Faster theming, cleaner code, fewer surprises.
Client
Year
Project overview
Scope: Build a themeable colour system, map to semantic roles, add component aliases, add dark-mode parity, remap all components, and document usage.
TL;DR - Colour primitives → semantic roles → component aliases → dark mode
Goal: Cut design time by ~50%, speed development similarly, map 100% of components to tokens, and meet WCAG AA across core screens.
My role: Project Lead (System architecture, naming, variables, dark aliases, component remap)
Team: Emmanuel Job, Nkiru Uba
Result: 100% components mapped to semantic tokens; normalised ramps (5→90); light/dark parity
Inconsistent UI slowed teams and confused users
Our design files mixed raw primitive hex values into components, colour ramps were uneven (20, 40, 80…), and there was no reliable way to theme light/dark or switch brands. Every change meant hunting through layers and hoping nothing broke.
Components referenced primitives directly
Inconsistent ramps and brand steps
Unnecessary primitive colours
Dark mode was a “repaint,” not a theme
Custom theming took a long time to complete per project, which was not scalable.

Old colour ramps 1.1
We set a clear goal - one themeable system, light and dark
Normalise ramps to a consistent 5→90 scale for greys and brands
Introduce semantic roles so components never point to primitives
Ship dark aliases that mirror light names one-to-one
Document the system so future work is fast and predictable
Create business opportunities by adding custom theming for clients
I audited colours and normalized ramps before naming roles
Primitives → Roles → Components
Primitives: pure ramps (e.g., Colours.Gray neutral.5, 10… 90, Primary, Secondary).
Roles (semantic): intent names the team understands (e.g., colour.bg.card, colour.text.primary, colour.action.primary.bg).
Component aliases (optional): friendly pointers for slots (e.g., colour.comp.button.primary.bg → colour.action.primary.bg).
If brand changes or dark mode flips, we update roles, not components.

Simple token architecture diagram 2.0
Components now refecrence aliases instead of raw values
Greys: Switched to Gray neutral ramp (5→90) for canvas, elevation, borders, disabled.
Brands: Two families, Primary (purple) and Secondary (orange), each with hover/active steps.
Foreground on strong fills: colour.fg.on.brand.* Tokens guarantee readable text/icons on brand backgrounds.
Actions: three families — Primary (purple), Accent (orange), Neutral (grey) — plus Ghost and Link.

New Semantic variables snapshot 3.0
Light & Dark
We mirrored names across modes. No new nouns, just different aliases.

Light vs dark theme 4.0
Implementation
A focused, repeatable cadence over 4 weeks.
Week 1: Audit colour usage, list primitives in use, define naming rules
Week 2: Build variables (primitives), create roles, map all core components
Week 3: Dark aliases, action families, migrate remaining components
Week 4 +: QA pass (contrast, leaks), docs, library clean-up and attaching aliases to recent project files.
Every component that was remapped and documented
Role tables with “what it’s for” in plain English (bg.page, bg.card, text.primary, border.default, action sets).
Component alias map for buttons, inputs, cards, and overlays.

Semantic token documentation 5.0
Before → After
Before: hard-coded hex, uneven ramps, manual dark/custom theme edits
After: token-first system, normalised ramps, light/dark parity, faster theming

Before vs after 6.0



