At a glance

I bet that if AI agents generate every component, a design system needs no component library. It needs rules precise enough for a machine to follow and check.

  • 117

    accessibility failures blocked before merge, since July 2026

  • 2.5 days

    median time to first gate pass across 9 pilots, down from 9 days for the first to under 1 for the latest

  • 71%

    first-run gate pass rate, n = 186 pilot builds

  • 3

    North star

    products in production on a pinned release, including the AI agent and home hub, the entry point to every product

    The first design system reached 6 products in over six years. The spec inherited its asset pipeline, and the AI coding agents the spec depends on arrived late in those six years.

The problem

My first design system was a component library I built. It had 45 components, with React, Vue and Angular built simultaneously. I delivered it in four stages against a roadmap I presented at CTO level.

For three years I led a multidisciplinary team on our first design system. When both team members moved on, I was running it alone, and a library that needed every component built three times stopped being feasible for one person.

Running the static gate against existing products found hundreds of rule violations per product. Over six years, 6 products adopted it, to varying degrees, across three frameworks. Each of its components cost about 3.5 weeks across three framework builds, roughly two of them on the second and third builds. And nothing told an AI coding agent what on-brand or accessible meant.

Before

  1. Designer draws the component
  2. Handoff to engineering 1 to 2 week wait
  3. Central team builds and releases 4 to 6 week wait
  4. Product team adapts it
  5. Accessibility audit

After

  1. Team pins a release of the spec
  2. Their AI agent reads the contract
  3. Agent generates the UI
  4. The gate checks it in CI
  5. People review what machines can't judge

Drift: static gate on the 12 pilot products, mostly raw colour values, then off-scale spacing. Durations: delivery history of the first design system.

Options

I judged three options on five criteria: framework coverage, time to ship, drift control, maintenance cost and dependence on a central team.

Option A

Keep the first design system

Keep the library and its three framework builds.

Why it lost. With both team members gone, one person would have built every component three times, for low adoption.

Option B

Generate code from Figma

Generate components from Figma through a framework-agnostic JSON contract I piloted.

Why it lost. It generates from a picture, so the rules never reach the code.

Option C, chosen

Specify and verify

A specification that teams' agents build from, and a gate that checks the result.

What the pivot cost

  • 45 components of work on the first design system, set aside
  • Teams who had only just migrated to it
  • Its roadmap, which I had committed to at CTO level

How I handled it. The asset pipeline carried over intact and migrated teams got a token mapping from the first system. I re-presented the CTO roadmap with new milestones.

What I built

The spec covers 21 components and 6 layout primitives, with more than 90 releases since July 2026.

One spec, any framework

Tokens, component rules and the definition of done live in three plain-text files.

Accessibility lives in the tokens

Every colour pairing the spec allows is tested against WCAG 2.2 AA in both themes, and a failing pair blocks the release.

A release is a contract

Teams pin a tagged release, and a hash records exactly which spec they pinned. The gate checks every build against it.

Read a spec excerpt on focus rings

Focus ring rendering. Every focus ring, the outline drawn on :focus-visible, declares the spec's focus ring offset token, whether or not the element has a fill. Leaving the offset undeclared, which hands the gap to the browser's default, is off-contract. So is 0, a negative offset or any other value, even on an element where a flush ring would clear 3:1.

A flush ring fails WCAG 1.4.11 on filled variants. Measured against each fill, it reaches 1.31:1 on the default interactive fill and 1.51:1 on the accent fill. The offset places the ring on the canvas instead, where it measures 4.39 to 13.04:1 across all four surfaces a focused control may sit on. So the offset rule is what carries 1.4.11, not the colour choice.

Adapted from the spec's DESIGN.md, section "Focus ring rendering", spec v0.93.0.

Each visual choice is a named rule with a token behind it. Visual rules an AI agent can follow shows how.

The gate

I built a gate with Claude Code that blocks any change that breaks the contract.

  1. Static

    Reads the source on every commit. It flags raw colours, off-scale spacing, unknown tokens, broken type pairings and right-to-left breakage.

  2. Rendered

    Opens a real browser before merge. It measures contrast on real pixels in both themes and tabs through every control.

  3. Human

    Says where automation stops. A screen can pass the gate and still be the wrong screen, so reviewers spend their time there.

When the accessibility engine could not run on a page, the gate used to report a pass, so now a file the gate can't evaluate fails the run as incomplete. Read the full decision log entry in Running a design system with 15 agents.

Try the gate

Break a rule
Bar chart of screens built per pilot
Pilot progress
passes: static, rendered
checked 2 governed file(s)

PASS, 0 violations

A simulation of the gate's static and rendered checks, running in your browser.
See a real gate run
$ node src/cli.js --config fixtures/violating/gate.config.json

gate — validated against pinned spec v0.31.0 (manifest sha256 verified)
passes: static
checked 7 governed file(s)

bad.css
  bad.css:2  [colour]  raw hex colour `#ff0000` — use a semantic `--color-*` token
  bad.css:4  [spacing]  off-scale spacing `15px` — not a scale step; nearest is `16px` (use its `--spacing-*` / `--space-*` token)
  bad.css:12  [undefined-token]  `--color-nonexistent` is not a defined colour role token in the pinned spec
  bad.css:41  [tokenisable]  `linear` is for loops only — a transition takes one of the four cubic-bezier easings, because a constant-velocity transition reads mechanical
bad.html
  bad.html:5  [a11y]  <img> is missing required "alt" attribute (wcag/h37)
laundered.css
  laundered.css:17  [spacing]  hardcoded length `13px` in `--card-pad`, which is not a spec token — the value reaches governed properties through `var(--card-pad)` unchecked; use a spec token, or define this property from one
  laundered.css:41  [ancestor-custom-property]  `--ring-glow` holds a var() and is declared on `:root`, so the var() resolves on the ancestor and descendants in another theme inherit the wrong value. Declare `--ring-glow` on the component selector whose own declarations read it. DESIGN.md § Focus ring rendering.
unenforced-prose-rules.css
  unenforced-prose-rules.css:58  [logical-properties]  physical side `padding-left` — write `padding-inline-start` (`paddingInlineStart` in a style object) with the same value, so the layout mirrors in right-to-left locales. COMPONENT-STANDARD.md § 5 mandates logical properties.
  [… 50 further findings omitted …]

Exceptions applied (1) — auditable, deliberate opt-outs:
  exception-demo.css:3  [color]  reason: third-party chart lib requires this exact brand hex

FAIL — 58 violation(s): 13 colour, 16 spacing, 15 tokenisable, 3 undefined-token, 6 a11y, 1 bad-exception, 1 ancestor-custom-property, 2 logical-properties, 1 typography
EXIT=1
Run on 13 October 2026 with gate v0.65.0, against the repo's own fixture of deliberately broken code. The fixture pins spec v0.31.0 and the current spec is v0.93.0, so this shows how the gate behaves on a fixed test case. It is not a check of production code. I trimmed the findings list and replaced product names with generic terms. The header, exceptions, verdict and exit code are otherwise unedited.

First evidence

I had only checked the spec by reading it, so I tested it by building from it.

  1. Build

    Three fresh AI sessions got only the pinned spec and built the same components.

  2. Measure

    All three failed the rendered gate on one button state no static check could see.

  3. Fix

    I corrected the rule, and every release that changes a rule now gets this test.

Ghost buttonActive state, as specified3.34:1Fails AA
Ghost buttonActive state, corrected5.17:1Passes AA
Recreation. Ratios are calculated from the colours shown. AA needs 4.5:1.

Caught by the gate

A rule that broke its own standard

The button rule gave its label 3.34:1 against 4.5:1 on its active surface, and the same paragraph cited that ratio to ban the pairing elsewhere.

Missed by the gate

A rule that contradicted itself

A table rule asked for two things that can't both be true. The agent picked one, wrote clean code and noted the conflict, so the release process now reads the agent's notes too.

n = 3 builds with one agent. A Cursor rerun passed two of three first time, and the gate caught an invented token in the third. This supports the method, but the model itself still needs adoption data.

Adoption

Engineering leadership made the spec the default for new UI and reskins, with no mandate to rewrite existing screens.

Why redo work we've only just finished?

Teams who had only just moved onto the first design system

What changed their minds

Speed

In pilots, a screen took a median of 1.5 days vs 5 through the old handoff, across 5 screens built both ways. Most of the saving was handoff wait removed.

No cost to them

I built the first three implementations. Later teams self-serve with a one-command setup.

The outcome was “We love it, but we can't do it right now.” Rollout follows each team's roadmap. A funded reskin was the forcing function, and mandates and demos converted nobody.

  1. 9piloted
  2. 6committed with a date
  3. 3in production on a pinned release, including the AI agent and home hub

Of about 60 teams, counted for the weekly report.

Designers now build in the same session as the agent. So far, 7 designers have contributed 47 catalogue entries, and Running a design system with 15 agents shows how.

Where I changed my mind

I argued rollout should wait until the component gap list closed. Then a smoke test found six contract defects review had missed, so pilots started with the first component batch and became our main way to find them.

We stopped arguing about pixel values in review. The gate settles that, so review is about whether the screen works.

Front-end lead, pilot product

Measures and trade-offs

North star. Products in production on a pinned release, counted weekly.

Time to first gate pass2.5 daysMedian across 9 pilots
Screens built in pilots14412 per product across the 12 pilot products
First-run gate pass rate71%Over 186 pilot builds, mostly failing on spacing
Drift on governed filesZeroDrift across whole codebases of the 12 pilot products is about a third lower than the baseline in The problem
AA failures blocked before merge117Since July 2026

Guardrail. I watch how long a critical fix takes to reach every adopter. Tagging one takes under 1 working day, and reaching all three products took 6 working days.

Next: reach 1.0. Four of six exit criteria are met; still open are a first-run pass rate of 75% or more over 100 builds and one quarter with no token contract change.

Trade-offs

The decision log records what each major choice costs.

AI output varies from run to run

Cost

Three runs from one brief used the same tokens and values but structured the code differently, from 407 to 930 lines.

Contained by governing the contract, not the code

The gate checks tokens, contrast and behaviour, which held steady. Product teams own generated code through their own reviews and tests.

Existing products weren't built for this

Cost

Most early adopters have a shipping product, its own CSS and a designer with expectations.

Contained by writing the guide for existing products first

It names the four problems they hit, in order. Server-rendered legacy products take about 2 to 3 weeks to a first screen.

The gate can't prove complex widgets behave correctly

Cost

The gate checks tokens, contrast, focus and ARIA, but not whether a complex widget behaves as users expect.

Contained by splitting the checks between the gate and people

Six of the contracts are complex widgets. The gate checks all six for tokens, type scale and static ARIA, and its rendered checks cover names, roles and text contrast. For the modal it also tests the focus trap, Escape and focus return. People check the rest, such as announcement order, typeahead, arrow-key focus and dismissal.

Pinning slows critical fixes

Cost

Pinning protects teams from unplanned changes, so a critical fix waits for each team to update its pin. That wait grows with every adopter.

Contained by updating pins through automated pull requests

Every tagged fix opens one for each adopter. Teams still review and merge it, so the pin keeps protecting them.

Reflection

Remove the cost of saying yes

Teams said yes once the implementation work wasn't theirs.

Retire your own work when the evidence says so

Sunk work is a poor reason to keep a product alive.

Next: Visual rules an AI agent can follow