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.
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.
Four decisions held the whole thing together:
- 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.
- 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.
- Contracts are platform-neutral. They bind role names that are identical on every platform. A platform gets a compiler, never a forked contract.
- 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
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
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
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"
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
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
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 --stagedblocks 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 diffto 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
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:
/grillquestions 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./refineruns 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-changeanswers "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./daemonis 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
Inspect Detail
| Before (April 2026, v0.1) | After (October 2026, v0.21) | |
|---|---|---|
| What ships | Token values as JSON, CSS variables and a Tailwind preset | Values, 120 semantic roles and 32 component contracts (17 stable) |
| What "correct" means | Taste and screenshots | A contract per component and a compiled reference to diff against |
| Platforms | Web | CSS, Tailwind, iOS (UIKit + SwiftUI), Android (Compose + XML), React Native, Figma, Yahoo's component platform, agent build packs |
| Drift detection | None | Checked at every edit, commit, merge and upgrade |
| Adding a word | A commit | An interrogation and a decision record |
| Agent builds | Guesswork | 73/73 bindings from the contract alone |
| Upgrades | Silent | A release opens a bump PR, and a moved contract holds the gate until the component catches up |
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.
