Case study

A universal, framework-agnostic design system across four teams

Four product teams each built their own components, so the brand looked different everywhere. A token-first design system, adopted incrementally through workshops and pairing, reached 90% adoption and cut brand-change rollout from weeks to hours.

Naseebullah AhmadiSenior Software Engineer, London
2 yrs 6 mos
6 min read
Visit the live site
Component variations
1000+
Team adoption after 12 months
90%
Brand-change rollout
weeks to hours
Component libraries
4 to 1
Runtimes unified
4

Context

I was one of two senior engineers on a dedicated design-system team, alongside one UX/UI designer. Four product teams, three on web and one on mobile, had each been building their own components from scratch.

There was no shared library to diverge from; there was nothing shared at all. Every team's buttons, inputs and layout primitives were built independently by whoever picked up that ticket, each styled from their own reading of the brand guideline. The result was visible design inconsistency between products that were supposed to look like one company. There was no way to support more than a single brand either, since colour, spacing and type weren't defined anywhere central enough to vary.

The constraint

The three web teams split across #react and #vuejs, and the mobile team shipped separate native apps for Android and iPhone. Any fix had to work identically across four runtimes with nothing in common at the code level.

It also had to be multi-brand from the start, not bolted on later, because the whole point was to stop hard-coding one brand's values into every component. And it couldn't wait for a finished product to prove itself: a system that only shipped value after a year of silent building would have lost the room long before launch, in an organisation that had already watched one cross-team initiative stall out. It had to earn adoption incrementally, component by component, while it was still being built.

What I did

  1. Design tokens (Style Dictionary) to Team's own components
  2. Design tokens (Style Dictionary) to Design system, grown incrementally
  3. Team's own components to React
  4. Team's own components to Vue.js
  5. Design system, grown incrementally to React
  6. Design system, grown incrementally to Vue.js
  7. Design system, grown incrementally to Android
  8. Design system, grown incrementally to iPhone
Tokens shipped first; the design system grew around them as teams migrated

I treated this as two separate products with two separate adoption curves, not one big release.

  1. 1

    Ship tokens first

    Used Style Dictionary with separate compiled exports for web and mobile: a team could pull in brand-correct colour, spacing and type the same week, without waiting on a single component.

  2. 2

    Grow the library incrementally

    Built the component library on top of that token package a handful at a time, not as a finished 1000-variation system dropped on day one, releasing each one as soon as a real team needed it for a real screen.

  3. 3

    Make adoption a relationship, not a release note

    Regular team syncs, company-wide demos, and hands-on workshops. Live pairing, sitting with an engineer and migrating their actual component in front of them, moved the needle most.

  4. 4

    Rotate engineers through the design system team

    Each team seconded an engineer for a sprint or two. They shipped a real component back to their own team and left as a built-in champion for integration questions.

  5. 5

    Make adoption measurable, not debatable

    Wrapped every component in a #mixpanel tracker, and audited every repo's package.json for the tokens and design-system packages rather than relying on self-reported status.

  6. 6

    Hold the team to two explicit SLOs

    Visible to the teams it served, not just tracked internally: 90% adoption within 12 months, and under two sprints at the 95th percentile for API-flexibility requests raised on a single Jira board instead of scattered Slack threads.

  7. 7

    Back every release with a real test net

    Unit tests, React Testing Library, end-to-end tests, and #chromatic visual regression across Chrome, Firefox and Safari, gated in CI, with a live #storybook instance for docs and diffs.

  8. 8

    Automate the mechanical parts of migration

    Wrote jscodeshift codemods for prop renames, import paths, and class name swaps, so pairing time went to the genuinely tricky cases.

  9. 9

    Put a sunset date on the old components

    Agreed with each team's lead up front, once the replacement had shipped, so migration had an actual deadline.

  10. 10

    Keep Figma and code in sync

    Synced #figma token values into the Style Dictionary source via a CI check, so a change made in Figma failed the build until it was reflected in the token package.

Outcome

Both SLOs held. 90% of teams had migrated onto the shared system within twelve months, measured directly from the package.json audit rather than self-reported status. Request cycle time stayed under the two-sprint target at the 95th percentile once the Jira board gave requests a single queue instead of scattered Slack threads.

The adoption curve tracked the human investment almost exactly: it moved fastest in the months with the most workshops and pairing sessions, not the months with the most components shipped. A brand change that used to mean four teams each hand-editing their own components now means editing the tokens package once and cutting a release; rollout went from weeks to hours.

The clearest shift was qualitative. Product engineers stopped raising design questions in the first place: Storybook and the Chromatic snapshots answered what used to be a back-and-forth thread, and that time went back into their actual product work instead. The remaining year and a half went into holding that bar: closing the last stubborn 10%, growing the component count past 1000 variations as new screens needed them, and keeping the system multi-brand as TrueLayer's own brand requirements evolved.

Solving something similar?

I take on select engineering work like this.

Work with me

What I'd do differently

  • Start workshops and pairing alongside the first components, not once there was enough system to justify a session: adoption only accelerated once it became a recurring conversation, and that lesson cost several slow months up front.
  • Build the Figma-to-tokens sync from day one. Design and code drifted apart a second time, the same failure mode this project existed to fix, in the gap before that check existed.
  • Agree each component's deprecation date the moment its replacement ships, not months later: asking upfront is an easier conversation than asking after a team has resettled into the old one.
  • Spread integration support across more than one person sooner. For the first few months I was the only route to an answer, so teams queued behind whoever I'd paired with that week.
  • Make adoption numbers visible to every team continuously, not just brought to all-hands periodically: a live dashboard would likely have kept the same pressure a demo created.

Learning points

  • Ship the substrate before the surface. Tokens went out alone, weeks before the first component, and gave every team a low-effort reason to touch the system early, which made every later component migration additive instead of a rewrite.
  • Adoption is won face to face, not in a changelog. The adoption curve tracked pairing sessions and workshops far more tightly than it tracked how many components existed at any given point.
  • Instrument before you need the argument. Wrapping every component in analytics from its first release meant adoption was never a debate, it was a chart anyone could pull up.
  • A visible queue beats an implicit one. Once API-flexibility requests had one Jira board instead of scattered Slack threads, teams trusted a request would actually get answered, and stopped quietly forking the component locally instead.
  • Deadlines move teams faster than invitations do. Nothing shifted the last holdout teams as reliably as an agreed sunset date on the components they were still running.