Running a design system an AI writes against
I led the design system for an enterprise security platform. Its highest-volume consumer stopped being a person some time ago — and almost everything about how you govern a system changes once that is true. Including how you answer the question an engineering leader will actually ask: what did this buy that a component library off the shelf would not have?
A design system is a set of rules for how a product should look. People used to be the only ones reading them. Now an AI reads them too, and it builds far more screens than any of us do.
An AI reads differently from a person. It never asks what you meant, it never notices that a rule is vague, and it never leaves a blank. So the documentation had to change, the order we build things in had to change, and we had to start measuring whether the system actually held.
Getting that right turned out to be worth roughly a fifth of the time it takes to produce a screen.
Contents
A design system has always had two audiences: the designer opening the Figma library, and the engineer reading the spec. A third one arrived, and it consumes documentation faster than both combined.
The difference is not that the model is worse. It is that a person and a model fail differently. Hand a designer an under-specified rule and they ask, or they copy a neighbouring component, or they leave it and flag it in review. Hand the same rule to a model and it produces a confident, plausible, wrong answer — and then repeats that answer consistently enough to look like a decision.
Which means the two things a system is normally judged on — is it beautiful, is it adopted — are joined by a third: does it survive being generated?
Everything below follows from taking that question seriously.
Back to the engineering leader’s question, because it is the one that decides whether any of the rest gets funded. My honest answer to it, for far too long, was directional — faster, more consistent, fewer rounds. Directional is not an answer. Someone deciding where two headcount go needs a number with a method behind it, and “let me get back to you on that” loses the room before you have finished saying it.
So: the number, and the counterfactual it is measured against.
component library alone
after testing the rules
before and after
per screen
The first figure is the whole argument. A component library on its own — ours, and I’d expect the same of any other — got generated UI about a quarter of the way onto the system. The library is the part you can buy. The remaining sixty-five points arrived in two moves, and the split between them is the part worth carrying.
Writing the rules so a machine could not misread them — exact values rather than relative descriptions, component status encoded where the work happens, a style layer bound to the production tokens rather than restating them — took it to 65%. That is where the first draft of the kit stood, untested. It is not something you can buy, because it has to describe your product rather than somebody else’s.
The last twenty-five points came from testing those rules by generating from them and rewriting wherever the output came back wrong. It was worth twenty-five of the sixty-five points.
Between the first two states the component set did not change; the only thing added was the machine-readable documentation. That step is forty of the sixty-five points. Between the second and third, two things moved together: the docs were corrected against their own output, and more components got docs. I don’t split that last twenty-five. Every figure here — the percentages, the correction times, the regeneration rounds in §07 — is my own judgement of my own output, against guidelines I wrote, with no control and no second rater, and I didn’t keep a count of the screens behind them. Where I could count, I did: the drift lab runs the same method in public, with the prompt held constant.
That split matters most if you have no design team. The forty‑point step is documentation an engineer can read with no designer in the loop, so it transfers — though somebody still has to write it, and that somebody is a designer. The last twenty‑five needed someone correcting the docs against what they generated, round after round; that part is work rather than an artefact. Writing the first and running the second is what a pilot is for.
The step that moved is correction: pulling generated output back onto the system by hand. It ran around two hours a screen and now runs about fifteen minutes. Correction sits inside a screen that takes roughly eight hours end to end, so the saving is a little over a fifth of total design time.
The accuracy percentage and the hours saved are not two findings. They are one thing measured twice — at 25% on-system a designer rebuilds three-quarters of it by hand, and at 90% they check it.
Published design-team efficiency gains from design systems run 31–50% — practitioner and vendor figures, collected by Smashing Magazine, not peer-reviewed work. Mine sits below that band and I would rather it did. An earlier version of this arithmetic used a three-hour correction average and landed on 34% — exactly one of those published figures, which is too convenient a place to land. Revising my own average down to two hours cut the headline figure by a third and changed none of the conclusions that rested on it. A number that survives being wrong is worth more than a larger one that doesn’t.
On the engineering side there is one measured instance and no rollout. A page went from Figma to a running implementation in about an hour, once its components were mapped to code, against a conventional estimate of two to three developer-days. That is a single page; the mappings were mine; and the hour covers design to correct composition, not design to done. I would say all three of those things before quoting the first one.
The figure worth carrying into a different organisation is not my total — that depends on a former employer’s volume, which is theirs and not mine to publish. It is the unit underneath it: about 1.75 hours of design time returned on every screen, before anything engineering-side. Multiply that by your own volume rather than trusting mine.
And what the arithmetic will not support: any claim about organisational adoption, engineering hours saved at scale, or what happens once the people prompting the system are not the people who built it. Those want measuring, not modelling.
The figures have a sensitivity table behind them, and payback stays under six months in every row — including the one where all three assumptions are cut at the same time. An earlier version of this model, a third larger, paid back in the same window. That the answer barely moves when the inputs do is worth more than the answer. Three estimates carry it, and they are mine rather than measurements: two hours of correction per screen before the kit, anywhere from thirty minutes to four depending on the screen; about eight hours per screen overall; and the number of screens a year. I am happy to walk anyone through the model who wants to push on those.
A number like 90% implies a straight line. It was not one. Three decisions made the rest possible, and I would defend the first two the same way today.
The layer before the backlog. There were known defects filed against components already built and in use. The obvious order is to fix what is broken before building what is next, and I argued the other way. The machine-readable layer moves the correction into the documentation, and tests the documentation by generating from it. A component is built from its doc on a test site and rendered; where it renders wrong, the doc is what gets edited. Once a doc produces the right component it is a verified specification — and a verified specification is what the already-deployed components then get corrected against. Fixing the defects directly would have fixed those defects. Building the layer produced the instrument every later fix was made with.
The honest counter, which I did not have a clean answer to at the time: people were hitting those defects while I built the layer, and asking someone to wait on a second-order benefit is a real cost paid by someone other than me. What makes it defensible is only that the second-order benefit was measured afterwards rather than assumed in advance.
An audit before a refresh. The brief was to move the system from an older visual language to a current one, and the available path was per component — corner radius, dark mode, ship, repeat. I ran the whole product first and let what came back decide the order instead. What it found was that the expensive problems were not in the components. They were underneath them, which is what the next two sections are about.
The pause, which is the one that has not paid. The platform spans two generations of a frontend framework, and the design system could only reach the newer one. Shipping into it would have left one half of the product looking current and the other half not.
The evidence that settled it was sitting in the tracker — UI‑inconsistency reports, filed one at a time and never aggregated. I had chased the first twelve by hand across four releases, and the count kept climbing. Nobody had put them together into an argument; I did, and that is what showed the shape. Individually each one reads as a defect. Together they read as a forecast. I took that to my manager. We agreed to pause, and I designed the carve‑out: ship only what the older generation had no equivalent of, which meant charts and data visualisation went out while everything with an old counterpart waited. Progress continued exactly where it could not fragment.
The decision assumed the migration would follow. It did not, and a system I had made ready sat behind a dependency I did not control.
Visible inconsistency is a forcing function. It makes the debt legible to whoever has to fund the migration. Pausing made the debt invisible, and invisible debt does not get prioritised.
That reading is the one I cannot dismiss: protecting consistency may be part of why the migration never became urgent, which would make the decision partly responsible for the condition it was waiting on. Does this make the problem easier to ignore? is a question I did not ask at the time and would ask first now.
Most design systems keep governance in a document nobody opens. The status of a component — approved, buggy, deprecated, do-not-touch — lives in a wiki, and the Figma file quietly disagrees with it.
I moved the lifecycle into the file structure itself, so a component’s status is visible before you can use it:
It looks almost too simple to count as governance. That is the point. A rule that lives where the work happens gets followed; a rule that lives in a document competes with the work for attention and loses.
It matters more once a model is involved, because a model has no way to ask whether a component is safe to use. It will happily instance a deprecated component or an experiment if nothing in its context says otherwise. Encoding status structurally means the answer travels with the thing.
Governance that lives in a separate document is governance you are choosing not to enforce.
Which operating model, named
Design-system governance comes in three shapes, and it is worth saying which one this was rather than leaving a reader with the vocabulary to guess. Not standalone — the system had consumers who had not built it. Not centralised — I was not the only person who could add to it. Federated: five designers contributing into one system with a single owner, none of them reporting to me.
The statuses above are what make that shape survivable. A federated model without a visible lifecycle policy produces a library where nobody can tell which parts are load-bearing, and the failure is quiet — someone builds on an experiment and finds out two releases later.
The direction most contribution models leave out
A contribution model that runs only designer-to-designer documents what designers already know. The more valuable direction is the other one. An engineer implementing from the specs came back with values that existed in the code and in nothing I had written — orphans the documentation had no opinion about. Sitting with her and working through them is how they surfaced at all; they went into the token mapping, so the next person generating against those guidelines got a system that knew about them.
Two things follow, and the second is the more useful one. Staying in the loop after handover is not diligence, it is the only mechanism by which a specification finds out what it is missing. And part of that last stretch from 65% to 90% in §02 is not documentation getting better in the abstract — it is documentation acquiring things it had been silently missing, because somebody building from it said so.
A specification cannot discover its own gaps. The person implementing it can.
The limit worth stating. A federated model with a single owner is only ever as strong as that owner’s availability, and mine is no longer part of it. The lifecycle policy is structural and should hold on its own; the contribution loop depended on someone being there to close it. I would not claim it has been tested without me.
The most expensive mistake available in a design system is authoring a shared thing second.
A concrete case. A container mechanism — a slot that holds consumer-supplied content — was going to be shared by three components. The tempting move was to build the first component that needed it and let the slot emerge from that work.
So the slot was pulled out and specified on its own first: what a slot is, what a consumer may inject, and where the boundary sits between container-owned chrome and consumer-owned content. Each component then became a short spec that says “a slot container, positioned like this, dismissed like that.”
The same discipline catches a subtler failure I have come to think of as a borrowed token doing double duty. A token gets reused for a second purpose because the value happens to match. The moment either purpose needs to change, you cannot move it without breaking the other, and the only fix is to unpick both.
Values matching is not the same as values meaning the same thing.
A type ramp is where a system either states its domain or hides it. Ours carries the usual semantic scale — headings, body, meta — and then a second scale that exists purely because of what the product is:
A security platform is mostly numbers under pressure — counts, percentages, thresholds, deltas. Treating stats as body text and scaling them by eye is how dashboards drift into inconsistency, so they get their own ramp, paired with a name/value convention for the label-and-figure pattern that appears in every panel.
The link scale earns its place the same way. Links exist inline in a sentence, inline in a dense table, and standing alone as a button-like action. Those are three different jobs, so they are three defined styles rather than one style plus judgement — because judgement is exactly what a generator does not have.
On top of the system sits the part that makes it machine-consumable: component guidelines written so a model cannot misread them, specs carrying exact values rather than relative descriptions, and a style layer bound to the production token file rather than restating it.
The test is blunt. Regenerate a page from the guidelines alone, name no fix in the prompt, and count what lands.
after testing and rewriting
to ship-quality
the system
covered
Those figures are my own reading of our system, not instrumented telemetry, and I would describe them as approximate — the same judgement, and the same limits, as §02 sets out. The method behind them is public and reproducible — I rebuilt it from scratch on a generic system so it could be checked without anything proprietary in it, and in doing so found that one of my own published claims about why it works was wrong. One more difference worth naming: the prompt is held constant there. It was not here, so some part of my own curve is me getting better at asking.
None of the above matters if the system is not used by people who did not build it. That is the half with no craft in it, and it is the half that decides whether the rest survives.
mappings, in a month
one spec, three teams
shipped instrumented
lead guide, six weeks
Five designers worked on and against the system, across every product area on the platform. So the day-to-day was less about designing components than about the things that decide whether a system holds: reviewing contributions, keeping the library and production honest with each other, deciding what was worth standardising and what should stay local, and coaching the people whose work depended on it.
Three of those had outcomes I can point at. One designer went from occasional contributor to owning the icon set outright. One was promoted into wider scope. And a guidance practice I had built was picked up by leads senior to me — which is the version of teaching that is hardest to arrange, because nobody above you has to take it. How that works without a reporting line
Adoption is built, not requested. Engineering could consume the system directly because 111 components were mapped to their code equivalents over about a month, so pasting a design link into an editor returned the real component — states, edge cases and accessibility already in it — rather than a picture to reimplement. Measurement worked the same way: one instrumentation spec pattern carried across seventeen requirement stories in three teams, so what shipped could be read the same way regardless of who built it.
Then you find out whether anyone used it. Six in-product guides shipped instrumented, and the lead one reached 427 unique users at 35% engagement in six weeks. Two things in that reporting are worth more than the number. One guide came back with zero engaged users and stayed in the report. And one metric read close to 100% click-through, which I footnoted so the room would stop believing it. The figure that flatters you is the one to check first.
The measurable version of adoption is not a dashboard. It is whether a new screen can be built without anyone inventing a value — and whether, when a model builds it instead, the result still passes an audit.
What I cannot tell you is whether it held. I am no longer on the team, and the real test of a system is whether it outlives the person who wrote it down. That one is running without me.
The components, screens, token names and product surfaces belong to a former employer and are not shown. What is described above is architecture and method: how the lifecycle is encoded, why shared contracts are authored before their consumers, how a token scale carries domain meaning, and what changes once a model is a primary reader of your documentation.
The measurement method is public in full, rebuilt on a generic system so it can be inspected and re-run by anyone: github.com/omkarpkh/design-system-drift-lab
The approach is also public as a working kit: Vela, a small token-first design system built in the open on generic components, with a React implementation where the design rules are documented and compiled — an invalid prop combination fails to type-check. The rules you already know go into the compiler, so review time goes to the gaps the rules do not cover yet. Playground · Source
One last thing, and it works against everything above. A widely quoted estimate — Standish, 2002, from four internal applications — puts roughly 45% of shipped features in the never-used column. So this makes a pipeline cheaper whose largest loss sits upstream of it. Making the wrong screen 22% cheaper is still the wrong screen — worth saying out loud before anyone treats a design system as a productivity programme.
I am happy to go deeper in conversation on any of it.