Turning a product that brands fork into a system that brands configure.
One travel-benefits platform. Multiple partner brands — card networks, banks and insurers — each needing the product to feel entirely their own. I built the design system that turned every new brand from a months-long rebuild into a configuration exercise — and the first launch on the model shipped with 89% less delivery effort.
Role
Design System Lead — sole system owner
Company
DragonPass
Timeline
May 2025 – present
Scope
Token architecture, component library, brand theming, accessibility, governance, tooling
Stack
Figma (variables, modes), Storybook, Jira, Claude + Figma MCP
IMG 01
One product screen shown in four brand modes side by side.
One component layer, themed per partner.
40+
components
650+
variants
897
live variables
7
brand modes
3
breakpoint modes
89%
less launch effort
Context
One product has to be many products at once.
DragonPass delivers airport-lounge and travel benefits on behalf of partner brands, and those partners set a high bar for invisibility: a cardholder opening the product from their bank’s app should never feel they’ve left the bank. One product has to be many products at once. When I joined in May 2025, there was nothing underneath making that cheap.
The problem
The design file wasn’t the source of truth for the product it described.
The system had to be retrofitted onto a shipped product. Screens had been designed, approved by clients and built before any shared foundation existed — so the standard couldn’t be defined and rolled out; it had to be reverse-engineered from live UI I wasn’t allowed to visually change. The original colour values weren’t on a logical ramp, which made systematic change nearly impossible. And because the product had been designed mobile-first, basic desktop states — input hover, for one — had simply never been created.
What existed instead of a system was an estate. Separate per-brand setups. Two codebases, web and mobile, for most modules. One module with no responsive version at all; another whose desktop design was a straight copy-paste of mobile; a third missing tablet breakpoints. Design and code disagreed on the names of the same things — “accordion” in design was “collapse” in code; “data group” was “overview”; “checkout” was “pay card” — and some components had been built in engineering with no design reference at all.
The cost surfaced everywhere. Applying a partner brand meant detaching screens and rebuilding them by hand — on one partner pitch, roughly two months of that work had to be compressed into two weeks. Token changes had no pipeline: each squad consumed tokens manually, so a single rename meant hand-edits and a QA pass in four codebases. Three overlapping status labels — error, danger, destructive — coexisted for two concepts. Accessibility was handled case by case. And with the whole product-experience group scheduled to start consuming the responsive product, the drift was about to compound, not settle.
IMG 02
Split image: Figma’s “Accordion” beside the codebase’s old “collapse”.
Design and code, disagreeing about the same component.
Constraints
Four things shaped every decision that follows.
The UI was client-approved, so I couldn’t change what things looked like — only what they were made of. Multi-brand requirements invalidate single-brand rules: button radius is unique per brand, so the classic “one radius token” doesn’t survive contact with this product. The mobile-first legacy meant desktop states had to be created retroactively. And I was the only person maintaining the system — so every quality check I introduced had to run without me policing it.
The architecture
Three Figma libraries in strict dependency order.
Primitives hold the raw material: colour ramps from 100 to 950, numeric values incrementing in twos. The Brand Switcher holds the semantic layer and the brand modes — the single layer engineering consumes. The component library builds everything against Brand Switcher tokens, never raw values. Toggle a brand mode and colour, type, spacing and corner radius all swap: one component layer, themed per partner.
The live Brand Switcher today: a Theme collection of 183 semantic tokens across seven brand modes; 686 breakpoint variables across three modes — Mobile/App, Tablet, Desktop; dedicated Semantic Spacing, Radius and Border collections — 897 variables in all. Even the grid is tokenised: responsive/margin, gutter, canvas, column — engineers count columns, never hardcode pixels.
Client configuration splits by client type: channel partners configure at the primitive level; white-label clients touch semantic tokens only, supplying as little as a primary and a highlight. The principle underneath: the system’s tokens stay stable and white-label configuration adapts to them — never the reverse.
IMG 03
The Brand Switcher variables panel: token names down the left, brand-mode columns across.
The semantic layer engineering consumes.
Try it
Don’t take my word for it — switch a brand.
Three components from the library, running on Atom’s real token values and real component specs, down to the regular-weight button label.
Live demo
One component layer. Every brand.
Atom's real tokens and real component specs, read straight from the system. Pick a brand — the same three components re-theme in place.
Button
Input
Tag
- background/interactive/action
- rgb(10, 35, 51)
- components/button/radius
- 999 · pill
- body/small/regular · weight/size
- 400 / 14px
- border/interactive/action-selected
- rgb(10, 35, 51)
Values read live from Atom's Brand Switcher, and the components match the library's real specs — down to the regular-weight button label. Partner brands are anonymised. Tab through for focus states; hover and press the buttons for their state tokens.
Decisions
Decisions that shaped it.
Naming is a contract.
My standard is an exact 1:1 between Figma and codebase — any developer should map code to the design system instantly. Where the two disagreed we picked the industry-standard term, validated against Ant Design and Material: “accordion” beat the inherited “collapse”. Inside components the same discipline holds — type for semantic values, state for interactive states, style for visual variants — because a property named state holding “success” is a future bug in someone’s head.
Three labels became two.
Error, danger and destructive coexisted for overlapping concepts. We consolidated: danger is the semantic token — red means more than system faults — and destructive survives only for destructive button actions. The live token set carries it end to end, from border/status/danger through background/interactive/danger-pressed to a new foreground/status/danger for informational red. Zero error tokens remain in the semantic layer.
The argument I lost — and wrote down.
I wanted every colour defined once in primitives with semantics pointing at variables, the way Ant does it. Engineering defines most colours as raw hex directly in semantic tokens, because a black button with a blue hover can’t derive from a standard ramp, and white-label clients should only ever supply two values. Their approach shipped. My job then wasn’t to keep relitigating — it was to record the decision so the debate stops resurfacing, and keep the trade-off visible. Systems work is knowing which arguments to close.
A structural rename that cost engineering nothing.
Consolidating action into interactive was executed as a structural rename, not a rebuild — zero components recreated in code. The entire interactive token family now lives under that group. A migration absorbed for free is an architecture win, and I count it as one.
Radius and behaviour became semantic because brands diverge.
Radius moved from per-component values to a semantic scale plus brand-scoped component tokens — components/button/radius exists precisely because button radius is brand-unique. The progress indicator, which once varied per brand with no rationale, is now governed by its own tokens: height, radius, active colour.
Guidelines before enforcement.
Spacing standardised on an 8px rhythm — including an audit walking stray 10px gaps back to 8 — but documented as guidance before being enforced as tokens; multi-brand constraints punish over-engineered rules. The same judgment shows up smaller: slots were added without removing the controlled variants designers rely on, and checkbox, radio and check-indicator were split into three component sets because merging them confused their use cases.
IMG 04
A changelog entry for the danger or interactive migration.
Every token change recorded — what, why, and where.
Accessibility
Accessibility as architecture, not review.
Accessibility in Atom is a first-class layer with an explicit ownership split: contrast and visual states are design’s; keyboard navigation and ARIA are engineering’s. The standard is WCAG 2.2, and the rules are concrete — a 24px minimum clickable area (I rejected a 12px close icon over it), focus rings on every interactive component including the desktop breadcrumbs where they were missing, contrast measured against the immediate surface a component sits on. One deliberate, documented exception: disabled states are excluded by design, not oversight.
Because I won’t scale a standard through review meetings, I built the checking into a tool: a custom Figma plugin mapped to WCAG 2.2 that audits components per state. And I ship it with its own test harness — an A11y Test Sheet of deliberately broken components with expected results documented, so anyone can verify the auditor catches what it claims to catch. I validated it against an established community plugin before trusting it myself.
IMG 05
The accessibility plugin flagging a deliberately broken button on its own test sheet.
The auditor, audited.
Governance
Governance that runs itself.
Every release is versioned and documented on changelog pages in both libraries — Atom 1.0, Atom 1.1, dated token-change entries — alongside a Token Table reference, so handover needs no re-explanation. Work-in-progress lives under bracketed namespaces so live designer files are never polluted, and “ready for dev” is treated as a promise: the label comes off anything unfinished. I published the same instinct publicly as Changelog Tracker, my Figma community plugin.
The library itself is organised for governance: core components grouped by type — actions, system feedback, data display, data entry, layout, navigation — then patterns, with a component index and documentation index as entry points. A dedicated taxonomy workstream with front-end aligned names, colours and tokens on both sides, and a Storybook layer gives design and engineering one shared surface. Documentation is generated, not hand-written: a Claude Code + Figma MCP pipeline reads the Brand Switcher and the library and produces a browsable docs site, component by component, brand switcher included.
IMG 06
Changelog Tracker’s Figma Community listing.
The governance instinct, published as a public plugin.
IMG 07
A component page in the generated docs site, mid brand-switch.
Documentation generated from the source of truth.
Tooling
AI is the system’s maintenance crew, not its designer.
Agentic workflows over the Figma MCP audit the gap between design and code — missing tokens, mismatched names, undocumented variants. Documentation, specs and change history are generated. The accessibility pass is a plugin, not a meeting. The judgment calls — what to name things, which arguments to close, what a brand may configure — stay human.
Proof in production
115 man-days versus 12.5.
Two channels launched toward the same destination. One was built the traditional, bespoke way. The other ran on Atom’s reuse model. The gap is the whole case for the system.
115
man-days — traditional bespoke build. Backend 55 · Android 21 · iOS 21 · HS 18
12.5
man-days — Atom reuse model. Frontend integration 6 · backend support 6.5
13
man-days — next channel on the same model, even as a high-customisation build
That is 89% less delivery effort — roughly 102 man-days given back on a single launch. And it wasn’t a one-off: the next channel on the same model came in at 13 man-days even as a high-customisation build — the pattern held.
The timeline told the same story in calendar form: the reuse-model channel started roughly three months later and still launched first — six days ahead of the traditional build.
IMG 08
The delivery comparison from my impact slides: two timelines, and the 115 / 12.5 / 13 man-day bars.
Same destination, one tenth of the effort.
Adoption
Adoption was the real design problem.
A system only works if people trust it. I ran one-to-one sessions with every designer to show Atom in practice, then a group share-and-learn tying it back to the business goal — every designer on the team is onboarded. I worked shoulder-to-shoulder with engineering, adjusting Atom where it genuinely needed to flex rather than defending it rigidly. And I led with evidence: documenting the delivery numbers turned Atom from a design initiative into a proven business tool — leadership’s own framing was that the data showed improved efficiency and cost reduction, a new way of delivering work.
The recognition that stuck with me came from the people using it: a product lead’s shout-out for jumping on a live call to solve an icon issue before it became everyone’s rework; kudos for bringing a stream of new components to life; a plain “high five all round” on Atom itself.
Outcomes
What the system delivered.
An 89% reduction in launch effort on the reuse model — roughly 102 man-days saved on the first launch alone, with the next channel confirming the pattern at 13.
A later start beaten by a faster ship.
Seven brand modes served from one component layer of 40+ components and 650+ variants, driven by 897 live variables across three breakpoint modes.
A semantic layer that got leaner as brands were added — 313 theme variables serving six brands in spring became 183 serving seven by late summer.
A structural token migration absorbed by engineering with zero rebuild.
Three status vocabularies consolidated to two.
An accessibility standard that checks itself.
A duplicated-code reduction of over 60% as squads converged on shared components.