Back to all projects
Design Systems

Building a Design System for an Agentic World at Yahoo

I turned Yahoo OS's design tokens from a package of values into a governed, platform-neutral spec: one authored contract per component, compiled to every platform and checked in every repo that builds from it.

My Role
Creator & Lead Design Engineer

Created the system and led it end to end: the token tiers, the contract schema, the multi-platform compiler, the CLI and drift gate, and the agent workflows the team uses to author and review it.

Impact
In six months, a token dictionary became a spec plus a gate. 32 component contracts compile to 8 targets from one source, and the gate is live in CI for all 14 components of Yahoo's component platform. A cold AI agent built the button from its contract alone with 73 of 73 bindings exact.
Building a Design System for an Agentic World at Yahoo Feature Cover
Inspect Detail

Key project pillars

One Spec, Eight Platforms
Each component is a single authored contract that compiles to CSS, Tailwind, iOS, Android, React Native, Figma, Yahoo's component platform and an agent build pack. Change a role once and every platform follows, with zero component edits.
73/73 Bindings From a Cold Agent
An AI agent with no prior context built the button from its contract alone in about 4.5 minutes and bound all 73 tokens exactly. The first MUI pilot mapped an existing button onto the contract with zero visual diffs.
Drift Caught at Every Boundary
A stamp-and-check gate re-verifies every registered component at each edit, commit, merge and package upgrade. All 14 components in Yahoo's component platform are registered, with the gate green in CI.

Challenge

In April 2026, the Yahoo OS token package was what most token packages are: a dictionary of values. It shipped every color, size and spacing step as W3C DTCG JSON, CSS custom properties and a Tailwind preset. That was real progress, with one palette and one scale. But a variable can tell you what purple is. It can't tell you how a button should use it.

So every team assembled components its own way, on web, iOS, Android and React Native. They used the same tokens and still got different buttons. Nothing defined "correct," so review came down to taste and screenshots.

Two panels. Left: the same "Get tickets" button rebuilt four ways on Web, iOS, Android and React Native, labeled hex hard-coded, radius re-typed, size by eye and hover dropped. Right: a guidelines page marked updated while three consuming apps sit pinned, forked or unknown.Inspect Detail

Drift ran in both directions. Code drifted from spec when a hex value crept into a component or a hover state got dropped. Spec drifted from code when a new version shipped and no consuming repo noticed.

Then the stakes changed. Teams started building UI with AI agents. Give an agent nothing machine-readable and it invents its own reading of the system, quickly and with confidence. What the system lacked was a governed answer to one question:

Which token paints which part of this component, in each variant and state, on every platform?

The Bet

Instead of shipping a component library every team has to adopt, ship the spec and a way to check against it.

If the spec is neutral and precise enough, anyone can build a correct component from it — including an AI agent — in any stack, without us shipping the component.

from the strategy I wrote at the start of the project

Four decisions held the whole thing together:

  1. Values are generated, never hand-authored. A person turns a dial in a recipe and everything derived from it updates. CI fails if a shipped value drifts.
  2. Contracts are authored, never scraped. A contract is a decision document, not a snapshot of today's code, so it can't turn today's accidents into spec.
  3. Contracts are platform-neutral. They bind role names that are identical on every platform. A platform gets a compiler, never a forked contract.
  4. Teams build from the contract in their own stack. A compiled reference exists only as the answer to check against, not as code to copy.

Process

The architecture: six stages, one direction

Six stages from left to right: Recipes, Primitives, Semantic roles, Contracts, Compile, and Your code plus gate, with counts for each and a lane showing that people turn dials and author while generation and compilation are automatic.Inspect Detail

Everything flows left to right, and nothing downstream edits anything upstream. Even the palette is computed. Twenty hue trajectories are solved against a shared 16-stop contrast ladder in OKLab, once per theme, against that theme's own background. Stop 9 is anchored to the brand purple's exact WCAG contrast on white (5.6653:1), so the brand color comes out exact instead of approximately right.

People touch only two places: the recipe dials and the contracts. Everything else is generated and CI-gated.

Three tiers, and color only through roles

A single brand color traced through three tiers: the spectrum primitive, the semantic role background-brand with its light and dark values, and the button's component token, ending in real compiled buttons in both themes.Inspect Detail

The build enforces one rule above all: no hex literals and no raw spectrum values in component work. Color always resolves through a Tier-2 role such as background-brand, which carries the light and dark values.

That rule paid for itself many times over. Dark mode, a compact density context and an opt-in "lit" expression layer all shipped as contexts that re-point existing role names. None of them needed new tokens or component changes.

One contract per component

An annotated button contract. YAML frontmatter for status, axes, grid, anatomy, tokens and sizes sits above normative markdown prose, with seven numbered callouts explaining each block.Inspect Detail

A contract is one markdown file. The YAML on top is what machines read, and the prose below is what people read. Both are normative.

I started with the button on purpose. It is the system's worst case: six colors, five prominences, four sizes, two width modes and full interaction states. If the grammar held there, it would hold anywhere.

Governance lives in the status field. draft can change at any time. stable means a person reviewed the rendered gallery and the approval is locked to the contract's spec hash. Change what the contract binds and the review opens again on its own.

Key decision: splitting "variant"

Before: 21 opaque variant names in three naming schemes, with primary and contrast-medium flagged as byte-identical. After: a color by prominence grid of real compiled buttons, with 20 declared cells and dashed placeholders for cells the button doesn't use.Inspect Detail

While refining the button live, I found that its variant axis was welding two decisions together. It had 21 opaque rows across three naming schemes, and two of them, primary and contrast-medium, were byte-identical. The root cause was an asymmetry in the palette: neutral had three solid fills while every functional color had two.

I split the axis into two small vocabularies. Color is the intent and prominence is how much of the shape the color owns. A contract now declares a grid of the cells it actually uses, and the compiler composes each cell's paint from shared rules. It was a breaking change, so it shipped with a machine-readable deprecation map and 130 aliases to give consumers a transition window.

Even the name was a vocabulary decision. I chose "prominence" over "emphasis" because "emphasis" was already reserved for a parked expressive layer. Small, closed vocabularies only stay small if every word is defended.

Testing the bet with agents

I ran the bet as experiments instead of debating it.

  • Web, greenfield. One prompt took a fresh agent to full conformance in about 15 minutes. It reproduced all 126 bindings verbatim, with zero corrections.
  • React Native, migration. The result was technically conformant, but it exposed the real gaps. Hover means nothing on touch, and the spec didn't say so. The agent correctly dropped hover, then cited a rules file as the authority for a rule that file didn't contain. That is the dangerous failure mode: agents fill gaps and stamp their guesses with false authority. The fix went into the shared vocabulary. Each state now declares what it requires (hover needs a pointer) and its fallback, so platforms derive the behavior instead of guessing.
  • Cold start. An agent with zero context built the button: 73 of 73 bindings exact in about 4.5 minutes, for roughly 74k tokens. The surprising finding was that reading the spec cost only about 15% of that. The rest was overhead from each round trip. Smaller files weren't the lever; fewer turns were.

That last finding produced the build pack: one self-contained file per component (yos pack button) with distilled rules, every token name with its exact web form, and the full contract inlined. Building one component means reading one file.

Every gap these runs found became a fix in the shared spec, never a per-platform fork.

One spec, every surface

The button contract at the center, wired to eight compile targets with real output from each: Web CSS, Tailwind, iOS, Android, React Native, Figma, Yahoo's component platform and the agent build pack, plus a reference oracle below.Inspect Detail

Because contracts bind names rather than values, each platform only needs a compiler. The same role reaches CSS as a custom property, Swift and Compose as generated constants, and React Native as resolved JSON.

Figma is a compile target too, not the source of truth. Variables and component plans are generated from the contracts and applied by a plugin. That ended the "Figma says one thing, code says another" argument. A shared Figma file has no branches, so plans apply only from merged main. That rule became a decision record after two branches clobbered each other.

The gate: register once, checked at every boundary

A stamp command writing a small manifest that pins the contract's spec hash, real yos check output catching a hard-coded hex, a raw duration and a misspelled token, a four-step track of edit, commit, merge and upgrade checkpoints, and three conformance levels.Inspect Detail

A contract only matters if it holds after the session that built the component ends. When a team builds or migrates a component, it stamps it. That writes a small manifest pinning the spec hash and the files that implement it. From then on, the same deterministic check runs everywhere drift can get in:

  • Edit: an editor hook re-checks the files an agent just touched, in the same turn.
  • Commit: yos check --staged blocks a hex value or a dead token name.
  • Merge: CI checks every registered component against the installed contract.
  • Upgrade: a package bump that moves a contract says so, with a yos diff to read. Releases also open the bump PR in consumer repos automatically.

Teams pick a level: Aligned (no hard-coded values), Conformant (every declared binding covered at a current spec) or Compiler-ready. One principle runs through all of it: machines block, agents advise. Only deterministic checks can fail a build. A known exception goes on the record with --waive and a reason, never in a code comment.

One deliberate line between people and agents

Three columns. Author, where people decide using /grill and /refine. A handoff marker labeled stable and hash-locked. Compile, which is deterministic. Consume, which is agent-run and machine-checked.Inspect Detail

Judgment is expensive, so the system spends it once, at the source. Everything downstream inherits it.

I built the authoring side as a set of Claude Code skills that work as part of the system itself:

  • /grill questions every proposed vocabulary change, such as a new token, axis value, color or state, one question at a time. A fresh-context counter-agent then argues against it, and the session ends in a decision record with a verdict the proposer doesn't get to choose. A pre-commit gate refuses vocabulary changes without one. Nineteen decision records exist so far.
  • /refine runs live contract review. A harness renders the component in HTML, React and React Native, and every edit rebuilds and repaints in the same conversation until the contract lands as stable.
  • /test-token-change answers "what if we change X to Y?" before anyone edits a file. It reports the blast radius across every component and cell, along with WCAG and APCA contrast scores. One partner team's proposal broke nothing but moved too much, and the report is why it didn't land.
  • /daemon is an advisory auditor. It re-verifies every tier binding and writes a dated report anyone can read.

Migration Is the Real Test

Greenfield builds passed easily. The harder and more valuable job is putting a team's existing component on spec in place, without refactoring the rest of their codebase.

I ran the migration path through four real codebases in rounds: a React Native app, two Next.js + Tailwind apps and a SwiftUI app. Each round's breakages became the next release. The fourth round graded the adoption flow honestly, B− on web and C on native, and 0.21 shipped the first half of the plan that grade produced.

The first MUI pilot mapped an existing MUI button onto the button contract with zero visual diffs: 17 of its 20 cells at level 2. The 3 cells MUI's model can't reach are recorded as 7 explicit waivers rather than hidden. The pilot also led to a clear recommendation: don't ship an MUI theme. MUI teams build overrides from the build packs, and the only MUI-specific bridge is a generated palette.

Impact

Six result tiles: 73 of 73 bindings from a cold agent, 126 bindings in the web greenfield build, 200 of 200 pixel-identical for a React chip, 14 of 14 components registered in Yahoo's component platform, 4 codebases through migration trials and 0 visual diffs in the first MUI pilot.Inspect Detail

Before (April 2026, v0.1)After (October 2026, v0.21)
What shipsToken values as JSON, CSS variables and a Tailwind presetValues, 120 semantic roles and 32 component contracts (17 stable)
What "correct" meansTaste and screenshotsA contract per component and a compiled reference to diff against
PlatformsWebCSS, Tailwind, iOS (UIKit + SwiftUI), Android (Compose + XML), React Native, Figma, Yahoo's component platform, agent build packs
Drift detectionNoneChecked at every edit, commit, merge and upgrade
Adding a wordA commitAn interrogation and a decision record
Agent buildsGuesswork73/73 bindings from the contract alone
UpgradesSilentA release opens a bump PR, and a moved contract holds the gate until the component catches up

A timeline from April to October 2026: a token dictionary in April, native exports in June, first agent builds and the variant split in July, contracts in August, then the drift gate, layers, migration trials and the MUI pilot in September and October. Totals below: 648 commits, 170 pull requests, 70 releases, 19 decision records and 32 contracts.Inspect Detail

Reflection

Ship the gate early. Ours landed in 0.17, five months in. Until then, staying on spec depended on people remembering to run a check. If I started again, the stamp-and-check loop would ship alongside the first contract.

Close the vocabulary first. The variant split was the right call, but it was a breaking rename across every contract. An afternoon spent on naming early saves weeks of migration later.

Test migration, not greenfield. Greenfield runs made the system look finished. Real codebases showed where it wasn't, especially on native, which still trails web.

Make tools say what they can't see. The worst failures in this project were confident ones: an agent citing a rule that didn't exist, or a check reporting clean on something it never read. Some of the most valuable changes made the gates state what they could not verify instead of passing silently.

Measure the real cost. I expected spec size to drive agent cost. It turned out to be round trips. Measuring before optimizing turned a vague goal ("make the docs smaller") into a concrete artifact: the one-file build pack.


Every figure in this case study is built with the system's own tokens, and every button shown is the compiled contract rendering live, not a mockup.

Built with the Yahoo OS Systems Team. From August, a teammate drove much of the Figma compile target and several component contracts.