Components got cheap and constraints didn't. An agent produces a styled button, a table and a chart in the time it takes to describe them, so a design system's value is no longer the components in it. Its value is what it encodes: the token architecture, the named states and the rules, with the gates that enforce them.
There's a simple test of how robust a design system is, and so of how consistent anything an agent builds with it will be: delete every component. If the tokens, the named states and the rules are still there, an agent can rebuild the components, and they'll agree with each other because they were made to the same constraints. Do it the other way round, keeping the components and losing the tokens and rules, and you're left with styled code and nothing to check the next change against. It starts to drift with the first edit. Components still ship, versioned, in packages, but they're an output of the system now, regenerated whenever it changes.
A design system was always a set of contracts that let two teams build one product without a meeting for every button. What has changed is who reads the contracts. Much of the code is now written by something that can't attend the meeting, won't ask, and follows whatever it finds in the repository. A rule kept in a review conversation, a design file or a separate documentation tool is a rule it never sees. A rule in a token name, a typed prop, a lint rule or a generated page is one it can't avoid.

Fewer surfaces, nothing to sync
We still build a component library. It became the easy part, so it gets less of our attention than it used to. What has grown instead is the tooling around design systems: a library in the design tool, a catalogue beside the code, a documentation platform, token plugins, connectors mapping design components to code ones, and now servers that hand all of it to an agent. Every tool is another copy of the system's decisions, and every copy needs a sync job and someone to notice when it fails. Most of the effort in the field goes on that syncing. We'd rather ask which of those surfaces a product actually needs.
Our answer is fewer of them, all generated from the code, so nothing has to be kept in step by hand:
| Surface | Where it comes from |
|---|---|
| Components | Built against the tokens, states and rules, and rebuilt when those change |
| Documentation | Generated from the code, inside the application, with prose kept for judgement |
| The design tool's variables | Synced one way, from the tokens in the code |
| An agent's context | Shipped in the same package as the code it describes |
That's what we build when we start a system. A client who already runs a design-tool library, a catalogue and a documentation platform keeps them, and we extend what's there in its own idiom.
Name every state
A dozen screens becomes eighty to a hundred and fifty states once stale data, partial permissions, denials and failures are real. One happy path generates five to fifteen more, and those are the ones people remember: the order that stopped halfway, the figure that is a day old, the action that needs someone else to approve it. Nobody draws a hundred and fifty screens, so on most projects those states get decided one at a time by whoever meets them first, differently each time. The inconsistency turns up in testing, or after launch.
We define a component as a set of conditions and keep the conditions as data. A pattern is a screen-level composition with a register of its named states, each one rendering the real components with the props that produce that moment. Signing in with biometrics has four. Entering a one-time code has five. Because the register is a list, one list drives four things:
- The documentation, one page per pattern, every state rendered from the live components.
- Visual regression, one snapshot per state per viewport.
- Accessibility, one run per state, not just the screens somebody thought to test.
- The review sheet: every state of one screen on one page, which is the artefact a designer can actually argue with.
Another state is then a line in the register, and the same line feeds all four. Full coverage used to be unaffordable because every state had to be drawn, and nothing has to be drawn now. Where we've run this, the register drives the documentation and a geometry check at six viewport sizes; the snapshot and accessibility runs are the same list pointed at two more tools.
A register also makes the work legible to an agent. It can be enumerated, so "build the denied state of the orders table" is a well-formed instruction where "handle the error cases" isn't.
What earns a named state is a change in what the person can do or what they're told. Denied, stale and offline change both, so each is a state. A longer name or a different currency changes neither, so it's a prop, and the register stays short enough that people keep it up.
The hard half can't be drawn
Stale, partial, denied, offline, locked, restored, interrupted halfway through a decision: on anything with real permissions or real money at stake, that's the hard half of the product, and none of it can be drawn. A static file shows a screen when everything worked. It can't show what happens when the figure is a day old, the connection has dropped, and someone else still has to approve the change.
So the register is where the design work happens. Every state renders from the live components, in a browser, at the sizes it will run at, and the decision about a denied state is made by looking at the denied state. An argument about something running settles faster than an argument about a picture of it. This is designers who build applied to the part of a product nobody can sketch, and the states compose from components that already carry their tokens and behaviour, so the work left is deciding what should happen.
Exceptions are responses
Named states only stay honest if the data layer admits the conditions exist. A service that returns success or failure pushes every exception back into the client, where each screen invents its own answer, and that's where most missing states go missing. The contract worth asking for carries the condition with the data:
| Outcome | What it carries |
|---|---|
| Ok | The data, and when it was read |
| Stale | The data, when it was read, and why it is old |
| Partial | The data, with the missing feeds named |
| Empty | Nothing, and the reason there is nothing |
| Denied | The reason, and a reference someone can give to support |
| Offline | What was kept, and whether it will be sent later |
| Error | The reason, a reference, and whether retrying is worth anything |
One renderer handles all seven, so no screen forgets one. Stale and partial still show the data, with a note above it saying what's missing and since when, because hiding a figure that's a day old is worse than labelling it. It's a design decision expressed as a type, and that's the general pattern: the closer a rule sits to the code, the more of it survives.
Tokens, and the one rule
Styled component libraries are now sold as the answer to agents writing code. shadcn/ui copies components into your repository, so you own every line from day one and inherit none of the constraints that would have made them coherent. Plex UI bills itself as the component library your agent understands, and arrives with fifty components, a three-tier token set and a design-tool kit of twenty thousand variants. Both solve the part that stopped being expensive, and both bring somebody else's semantic layer, component API and defaults, which is how a product ends up looking like every other product built the same way.
We take from outside only the layer that is genuinely hard and genuinely universal: focus management, keyboard behaviour, ARIA, the interaction work that is subtly wrong whenever it's hand-rolled and that no product is differentiated by. That means accessible unstyled primitives, with Base UI our current preference. Everything above it is where a product differs from every other, and it's now the cheap part to write.
| Layer | What it covers |
|---|---|
| Tokens | Colour, type, spacing, size, radius, depth, motion. The base vocabulary |
| Primitives | Buttons, inputs, typography, styled by us over an accessible unstyled foundation |
| Components | Composed from primitives, still content-agnostic |
| Patterns | Whole flows, each with the register of its states. A button is a component; signing in is a pattern |
That's a rule about what we introduce. On a client codebase already built on shadcn or MUI, that is the design system, and we extend it in its own idiom: a second vocabulary beside the first costs more than the one you inherited.
Within the tokens there are two kinds. Primitive tokens carry raw values: grey-50, blue-700, size-16. Semantic tokens name an intention and point at a primitive: background-subtle, text-critical, spacing-md. Components reference only the semantic layer.
A component built on grey-50 has fixed an appearance. It's wrong in dark mode, wrong on a second brand, and nothing in the name tells anyone why to change it. One built on background-subtle states an intention and leaves the token layer to decide what that resolves to per theme, mode and product. That one rule turns a second brand into a theming exercise, not a fork. It also carries the accessibility rules into everything above, because foreground and background tokens are generated and checked as contrast pairs, which is the structural half of our accessibility position. The tell is a primitive on anything interactive or structural: bg-blue-500 compiles and looks fine in light mode, has no foreground pair and no dark counterpart, and is how dark mode quietly breaks in month six.
Two more rules make the token layer worth having:
- Every tier, or it isn't a design system. Colour, spacing, size, type, radius, depth and motion. A generated palette over hand-typed everything else is a stylesheet with nice colours. Motion is the tier people skip: a small set of named presets, every duration and easing a token, each with its reduced-motion form, and no other way for anything to move.
- Generated, not hand-edited, and the code is the source. Tokens come from a seed and a command that can be re-run with a check mode, so drift is a failing build. Values flow from the code into the design tool's variables, the opposite of most token setups and on purpose: the thing that ships should be the thing that's true. The colour tokens also come out in the W3C's Design Tokens format (DTCG), so any tool that reads the standard takes the same values without anyone retyping them. That file is an export like the others; the code stays the source.
Products share the rules, not the components
An organisation with several products usually tries to share one component library across all of them, and spends its time on the arguments that follow: the product that needs a denser table, the brand that needs a different button, the team that forks to ship on time. We share the part that is hard to get right and keep separate the part that is cheap to make.
What crosses products is the token architecture (the tiers, the semantic names, the rule that components use only those), the rules with their checks, the shape of the state registers and the outcome contract, the accessible primitives underneath, and the gates. Each product gets its own token values and its own components, generated against that shared architecture. Retuning the colours for a new product is a run of the generator with a new seed, and generating its components is the same work an agent does on any project. The architecture and the rules are the hard part, and they're done once.
So products can look and behave differently and still be held to the same contrast pairs, the same accessibility checks and the same named states, which is the consistency the people using them actually notice.
Written where an agent will read it
Documentation is where design systems die, because it describes the system instead of being generated from it. It starts complete, drifts out of step with the code by month six, and stops being trusted.
The fix is mechanical: nothing is written twice. A component is authored once, as typed props, the note above them, the variant config and the names of its parts, and that one source becomes four surfaces:
| Surface | Who reads it |
|---|---|
| Autocomplete and inline guidance | An engineer, without leaving the editor |
| The documentation site | Designers and engineers, with the live component on the page |
| Lint and token enforcement | The pipeline, refusing a literal colour before review sees it |
| The context handed to an agent | Whatever is writing most of the code |
Only the last is new, and it's the one that changes what a system is for. The baseline exists because the code exists, so it can't fall behind. The specs an agent reads start hand-written, which is quick and proves their shape, and are then generated from the same source, which is the version that can't drift.
Prose goes in where it earns its place, and what earns it is judgement. It's written like a brand guideline, not a lookup table. A list of answers covers the cases somebody thought of. The language a design is argued in (what this product values, what a quiet state should feel like, why density beats whitespace on a data-heavy screen) lets a person or an agent reach the right answer in a case nobody listed. A type can't carry that, so generating the baseline frees the prose to be only this.
Prose must never restate the system. An instruction file that lists the components, the tokens or the variant names is a second copy of the code, and it's wrong the first time either changes. Southleft found exactly this benchmarking agents against design systems: one wrong tone name in an AGENTS.md failed ten of ninety generations, and runs with no instruction file scored higher on the mechanical checks than runs with one. The file an agent is handed points at the contracts and carries only the judgement.
Rules are handled differently from components. A rule is the part of judgement that can be checked, so it's authored beside the thing it governs, as an object carrying its statement, the token it rests on, what it applies to, the exceptions, and the check that proves it: every focusable element measures at least 44 pixels in both axes, no duration or easing literal anywhere in the application. The agent reads the rule and the pipeline enforces the same one, from the same object. Where a rule is ours and not the client's, a threshold we picked to make something buildable, it's marked as an assumption in the source and shown as one in the documentation until somebody confirms it. The guideline prose is the part that can't be checked, and the two are kept apart.
The documentation site lives inside the application, rendering the same components from the same registers, because a catalogue to one side documents components instead of the product they add up to.
A workaround is a missing API
An agent that can't do something with the system does it anyway, around the system. A password field with no slot for a show-and-hide control gets one bolted on beside it, in a sibling element with its own styling and its own idea of focus order. Every mechanical check can pass on that: it compiles, it uses tokens, it clears the automated accessibility scan. It's still wrong, and no documentation fixes it, because the agent didn't misunderstand the component. The component can't express what the product needs.
So we treat a workaround as a finding about the system. Review sends it back to the component, the missing slot, prop or state is added once, and the next agent finds it there. It's the one place where reviewing an agent's output improves the design system as well as the product, and it's people and agents learning together at the scale of a single component.
Same names on every platform
Where a product has a web layer and a native one, the temptation is one component implementation serving both. It produces a worse version of each. A web button is DOM, CSS and a pointer; a native button is a view, a style object and a touch, and code that tries to be both is code nobody can reason about.
What crosses the platform line is the vocabulary: the same token names resolved into the right form for each platform, the same component names, the same props, and the same register of states to test against. The rendering stays separate. Two implementations, one API, one list of conditions, and an agent working on one platform can be pointed at the other to find out what something is called.
How we build and run one
The packages
The system is built and released as a product in its own right, with a pipeline separate from the applications that use it. One repository, one task graph, each package with one job and one version, so a token change rebuilds and retests everything downstream of it and nothing else.
| Package | What it is |
|---|---|
| Tokens | Colour, type, spacing, motion, named by intention. Emitted for web and for native, and synced into the design tool |
| Icons and brand | One folder of source files. A package per platform, colour inherited, size from the tokens |
| Web components | The components, shipping their prop types and state names with them |
| Native components | The same names and the same props, built for the device |
| Patterns | Compositions, and the register of every state each one can be in |
| Shared logic | Formatting, display rules for permissions, and how a result is handled |
| Documentation | Generated from all of the above, for people and agents alike |
The packages publish to a private registry the client controls, and applications take the system by version, not by shared source.
Releases carry the context
The context an agent reads travels in the same packages. Each one ships its prop types, state names, rules and guidance inside the published version, so an agent working in an application reads the contract for the version that application has installed, not whatever the documentation site says this week. A documentation site or an MCP server can serve the same material, but the copy in the package is the one that can't disagree with the code beside it.
Releases follow semantic versioning. Tokens, icons and copy are a patch. A new component is a minor. A breaking prop change is a major and ships with the codemod that makes the change for you.
The gates
Nothing publishes without passing all four:
| Gate | What it does |
|---|---|
| Accessibility | Automated checks on every component, in every state and variant, before publish |
| Standards | Lint and token enforcement, rejecting a hardcoded value where a semantic token belongs |
| Visual regression | Every variant diffed on every change, so a token edit can't quietly alter something three layers up |
| API contract | Prop and interface checks, so a breaking change is caught before it ships |
Quality is checked once, in the system, instead of again in every application built on it. The gates run identically on a pull request from a person and one from an agent, so there's one way to contribute and neither kind of contributor is trusted more than the checks. Everything that can become a check does, and human review is kept for what can't.
The API contract gate matters more than it used to. Once something other than the application decides what a screen contains, from permissions or configuration, component, prop and state names become an interface that something calls. Renaming a prop is then a breaking change to a running system, which is why a design system is versioned software with a contract and not a file everybody shares.
Adoption
Mandating adoption fails; teams dig in when they're forced. They take up a system that is plainly better than what they have: guidance in the editor, checks that catch problems before review, accessible behaviour already handled, small bundles, and every standard a team would otherwise enforce by hand switched on by default. The rest is discipline:
- Backwards compatible, so migration runs screen by screen with no blocking rewrite, and nothing is removed in the release that replaces it.
- Versioned like software, with semantic releases, changelogs written for designers and engineers, and a maturity scale so teams know what's stable, emerging or deprecated. Teams pin a version and upgrade on their own schedule.
- Easy to contribute to, or teams route around it. If adding an icon needs an engineer, designers stop adding icons; if adding a state needs a designer, engineers stop adding states.
- Measured. Usage and adherence are reported on a regular rhythm.
Ownership once it's stable
Two roles, embedded during delivery, not trained at handover. A system owner on the design side looks after the token architecture and the visual language, arbitrates contributions and runs the reporting. A front-end steward on the engineering side looks after the packages, the release process and the pipeline.