Skip to content

Case study 01 · Platforms and design systems

A design system that keeps web and mobile in sync

I built CDS after running into the same five problems on eleven projects. It has 74 components and 678 tests, and this site runs on it.

EngagementOwn product · 6 months, ongoing · solo · this site runs on itPublished

74 / 678

the headline number

1 constraint

The same components had to run on Next.js, Vite and React Native without the web and mobile versions drifting apart.

The story in 5 steps

Stack

  • TypeScript
  • Next.js 16
  • React Native 0.87
  • Vite
  • styled-components
  • pnpm workspaces
  • Figma MCP
  • custom ESLint plugin

01 · Context

Web and mobile versions of a product start out identical, and two years later they share nothing but a name.

In CDS, design tokens arrive from Figma automatically over an MCP server.
The system has 74 components and 678 tests, and its 81-page catalog is generated from the source code.

02 · Five problems

I ran into each of these problems on a real project before I started building CDS.

Web and mobile drift apartIt moves the platform split one layer below the call site, so the code that uses a component never has to check the platform.
Figma and the code are two sources of truthDesign tokens arrive from Figma automatically over an MCP server, with a manual path as a fallback.
Conventions written in a README stop being followedEvery invariant has a check, and the build fails as soon as one is broken.
Every new product starts its component layer from zeroIt is a package that my financial platform already uses, and this site is built on it too.
Naive multi-brand theming multiplies the variantsComponents refer to colors only through token names.

03 · The principle

38 of 74 components have a native twin, which sounds as if half the system had diverged.

33 of the 38 twins import no other componentThey are leaves that translate a single platform element, div to View or input to TextInput, and do nothing more.
A twin is allowed for only three reasons, and two of them have nothing to do with elements.
A different elementThis covers 33 of the 38 twins.

04 · Numbers

Components74
With native twins38, roughly half of them literally the same file
Test cases678 across 62 suites
Catalog pages81, props tables generated from source
Verification tooling~2,450 lines plus 4 ESLint rules
Bundle, Box-only entry253,075 B → 241,309 B
Duration6 months, ongoing
In use bythis site, and my financial platform

05 · The review

I reviewed all 75 components the system had at the time and found 188 issues.

A label on an element with no roleaccessibilityLabel on a node with no role is announced by neither platform.
An inherited prop that never reaches the elementOmit<BoxProps, …> leaves id, a11yRole, accessibilityLabel and pointerEvents in the contract, but a branch that does not destructure them passes them into ...box, and from there into a style helper that returns only $ props.
A comment describing an earlier version of the systemA note describes a limitation, the limitation is later removed, and the note stays.

Related services

The services that draw on this work.

Related writing

Posts that came out of this project.

Case study 01

Working on a similar problem? We reply within one business day.

The first call takes 30 minutes, and you’re under no obligation.

Tell us about your projectcontact@microtech.consulting