Skip to content
SeDu Oh
All projects

Atlas

A design system six product teams share

Year
2024
Role
Lead — architecture and build
Team
4 people
Stack
  • TypeScript
  • React
  • Tailwind CSS
  • Storybook
  • Vitest

Six product teams were each maintaining their own button. Not metaphorically — there were six Button implementations in the monorepo, and they disagreed about focus rings, disabled opacity, and whether a loading spinner replaced the label or sat next to it.

Atlas is what replaced them. It is a 40-component library that every one of those teams now builds against, and it ships a token layer that changes all six products from one file.

The problem was not the components#

My first instinct was wrong. I assumed the fix was to write really good components and then convince people to adopt them. I spent three weeks on a beautiful Button and almost nobody switched.

The reason was boring: adopting it meant a visual diff on pages those teams owned, and nobody wants to sign off on a diff they did not ask for. The component was not the obstacle. The migration risk was.

So I inverted the order of work.

Tokens first, components second#

Instead of shipping components and asking teams to adopt them, I shipped a token layer that reproduced each product's existing look exactly. No visual change at all — the point was to make the change invisible so it could land without a design review.

/* Every product imported this and looked identical afterwards. */
@theme inline {
  --color-surface: var(--surface);
  --color-fg: var(--fg);
  --color-brand: var(--brand);
}

Once every product was reading from tokens, a design change became a one-file change instead of six pull requests. That earned the credibility to replace components one at a time, and this time the teams asked for it.

The lesson I keep reusing: adoption is a risk problem before it is a quality problem. Reduce the risk of saying yes and quality arguments start landing.

What the 40 components actually cost#

The interesting number is not 40. It is the test surface:

LayerCountWhere the time went
Primitives12Focus management, keyboard behaviour
Compositions19Documenting when not to use them
Layout utilities9Almost free; they are CSS with a name

The primitives took the longest per component by a wide margin, and it was all keyboard and screen-reader work — the parts you cannot see in a screenshot. Every primitive has a Playwright test that tabs through it, because that is the behaviour most likely to regress silently.

What I would do differently#

Two things.

I versioned Atlas with the monorepo, so a breaking change forced every product to update at once. That was the wrong call — teams should have been able to absorb a major version on their own schedule. Independent versioning would have cost some consistency and bought a lot of goodwill.

And I wrote the docs last. Storybook existed from day one, but the "when should I reach for this" prose came months later, and in the gap people made reasonable wrong guesses that I then had to unpick. Writing the guidance while the decision is fresh is cheaper than correcting the misuse later.