Your design system is documented. The AI does not read it.

Last week I wondered how well our own UI language is actually written down. We have a styleguide. We have tokens. We have a 997-line document describing exactly what an admin page looks like. Sounds tidy, right? Then I looked up how many hardcoded colour values sit in the code that the same document forbids.

Fifty-six.

That is not because the team is careless. It is because it is not an enforced rule.

A while back I wrote about organising your Claude Code rules in layers. Some of that might already be outdated, given the speed at which AI is moving, but most of the post still holds true. This one is about what I got wrong in it: I treated writing the rules as the finish line. I trusted the AI to do well, where I should have trusted code.

Documentation used to be for people. Now it is a contract.

Something changed in what a design system has to do. When people wrote all the code, documentation was a reference work. You read it properly once, it stayed in your head most of the time, and your colleagues corrected you at review time.

Now a portion of the code writes itself. And a model building a screen only knows what sits in its context at that moment.

This was measured at CHI 2026. Researchers compared three ways of handing a design system to an AI agent: paste the whole styleguide into the prompt, inject only the relevant sections, or have the agent assemble from a live component registry. They built six real interfaces and counted how often the system was followed.

The registry won at 95.08 percent. Prompt instructions lost. I have been trusting prompt instructions.

Now, ninety-five percent sounds very good. It means only five in every hundred screens leave the system, but they do so at machine speed. And here is where it turns nasty: the next agent reads those five as established precedent. One missed deviation becomes forty copies. Drift now compounds instead of flattening out.

This is a different problem from the one we are used to. Inconsistency used to be a quality issue. This kind of AI drift is a contagion issue.

The frontrunners stopped writing documents and started building structure.

Look at who is ahead and the same movement shows up. From prose to structure.

In October 2025 the W3C Design Tokens Community Group published version 2025.10, the first stable version of the token format. More than twenty editors, with Adobe, Google, Microsoft, Meta, Salesforce, Shopify and Figma at the table. Tokens went from a table in a document to an interchange file your tooling can read.

In April 2026 Google Labs open-sourced DESIGN.md. One YAML file for the tokens and Markdown underneath it for the reasoning. The thinking behind it is sharp: tokens give an agent the exact value, the prose tells it why that value exists. It ships with a validator that checks contrast ratios against WCAG and returns structured JSON.

But the most interesting example comes from Meta, and not for the reason you would expect.

In June 2026 Meta released Astryx, the React design system that serves roughly 13,000 internal applications. Over 150 components, eight years of maturing. The press coverage was about the size of it. The interesting part sits somewhere else: two things I have not seen done this consistently anywhere.

The first: their agent documentation is generated from the installed version. You run their CLI with an agents flag and it writes an AGENTS.md, or a .cursorrules, or a CLAUDE.md, pulled from the code you actually have running. That context cannot go stale. Our own 997 lines are written by hand.

What that generated context teaches an agent, incidentally, is not a list of rules but a working order. First find an existing page pattern. Then study its skeleton. Only then read the props of every component you are going to use. That is the registry approach from the CHI research, made practical.

The second, and this is the one that stopped me: they measure whether it works. Every night. They call them vibe tests. The same prompts run against Astryx, against Tailwind, against shadcn, and against raw HTML. A separate judging agent scores the output on six dimensions: correctness, accessibility, code quality, efficiency, maintainability and design. Deliberately not “does this look like ours” but “is this good code”.

What we changed in our design system

Back to those 56 colour values. I sorted them before counting them, and that paid off. Most were entirely legitimate. Google and Microsoft brand colours in the sign-in buttons. HubSpot orange on an integration card. Those are other companies’ brands. They do not belong in our token system.

The real violations sat in exactly one file. Thirty-two of them, all in the widget chat interface, all our own dark grey and ivory that should simply have been a token name.

So we wrote a lint rule that only fires when the colour value is literally the value of an existing token. The rule reads those values from our stylesheet itself, so there is no second list to go stale. Google blue is not a token of ours, so it stays quiet. The widget default is not in a class name, so it stays quiet there too.

Thirty-two hits. Zero false alarms. Zero left afterwards.

And yes, off-the-shelf tooling for this already existed. I should have looked first. What convinced me afterwards that writing our own was still right: the common rules operate on CSS files, and every one of our violations sat in the utility classes inside components. Those would have found none of them. And a rule that simply bans all hex values would have flagged all 56, Google and Microsoft included. Which is exactly the noise that gets a rule switched off inside a week.

So what did I learn?

The first: one colour value appeared in four places in the code. It now lives in one place, with a test that shouts the moment somebody retypes it.

The second: of our forty shared components, thirty-nine were properly listed in the document. That is well maintained. But I only found the fortieth because I went looking by hand. So there is a test on it now.

The third came from a rule I nearly wrote and should not have. Our document says never hand-roll a raw button element. There are a hundred places that do it anyway, and my first instinct was to call that a hundred units of debt. So I looked at them. Four of them actually resemble a button, and all four are correct: a circular avatar that opens a menu, the Google and Microsoft sign-in buttons that have to look the way those companies say, and some dense editor chrome that our smallest button size does not fit. The other ninety-six are clickable rows, toggles and tree items, built exactly the way our own documentation says to build clickable rows.

So the code was right and the sentence was wrong. If I had written that rule, it would have flagged ninety-six correct decisions, and somebody would have switched it off within the week, along with the two rules that do work. Measure before you enforce. The interesting part is what the measurement told me next: the same sentence also covers input fields, and there it turned out to be true. About twenty form fields skip our own components. That is a real clean-up, and it is a fifth of the size of the one I almost did for no reason.

What we actually built

Three checks, all of them small. The point was never the amount of code.

A colour rule that knows our palette. It reads the token values out of our stylesheet at lint time, so it holds no copy of the palette that could go stale. It only complains when a hardcoded colour is literally the value of a token we already have. That single decision is what made it usable: thirty-two real hits, and not one false alarm on the Google, Microsoft or HubSpot brand colours sitting in the same files.

A test that keeps the component list honest. It compares the table in our UI document against the folder of shared components, in both directions. A component missing from the table fails. A table row pointing at a component that no longer exists fails too. It found a missing one the first time it ran.

A test that ties the documentation to the stylesheet. Our token document loads into every AI session, for every file, so a wrong value in it is the most expensive kind of wrong: invisible, and read by everything. The test checks that every colour value quoted in that document still matches the stylesheet. We verified it fails when we deliberately break a value, because a test that has never failed has not been tested.

And one thing we deliberately did not build: a rule for those hundred buttons. See above.

Our design system does not need to get prettier. It does need code to enforce it.

Want to build this yourself?

Start with the reading, because most of the value is in the thinking rather than the code.

If you want the off-the-shelf route rather than writing your own rule, look at stylelint-declaration-strict-value for CSS files, or MetaMask’s eslint-plugin-design-tokens if your colours live in JavaScript. Try those before you build anything. We only wrote our own because our violations sat somewhere neither of them looks.

Take ours and feed it to your own AI

Our portal is open source, and the whole design layer is in there. You are welcome to point an agent at these files and say “do this for our codebase”, which is genuinely the fastest way to start.

The documents an agent reads:

  • .claude/rules/klai/design/tokens.md is the one that loads in every session, for every file. Colours, fonts, radii, logo rules, and the accessibility constraints that go with them. If you copy one file, copy this one.
  • .claude/rules/klai/design/styleguide.md is the shared brand DNA: the design philosophy, the colour reasoning, and a list of anti-patterns. It also carries a warning we had to add, because the same token name means different things on our website and in our portal.
  • klai-portal/frontend/docs/ui-standards.md is the long one. Every component, every layout container, the forbidden patterns, and a section stating exactly which rules are machine-enforced and which are not. It opens by declaring that it wins over any other design document, which matters more than it sounds: without a precedence rule, an agent simply believes whichever file it read last.
  • AGENTS.md at the root, and the smaller one inside the frontend, are the entry points that route an agent to the right document before it starts.

The enforcement, which is the part this post is actually about:

  • klai-portal/frontend/eslint-rules/ holds the colour rule and its palette reader. The reader is the piece worth stealing: it parses the token values straight out of the stylesheet, so the rule owns no copy of the palette.
  • klai-portal/frontend/tests/design/ holds the two drift tests, one for the component list and one tying the documentation to the stylesheet.
  • klai-portal/frontend/src/routes/dev/ui.tsx is our live catalogue: every owned component in every state, on a dev-only route. The written document is the rule, this is the rendered proof, and both have to change together.

The reasoning behind the changes sits in the pull requests rather than the commit messages, so #1139, #1141 and #1142 are the better read. The last one is the button story: what it looks like when the measurement tells you your own rule was wrong.

One honest warning before you copy any of it. We have the enforcement layer and we do not have the measurement layer. We do not run anything like Meta’s nightly scoring, so we cannot actually tell you how often an agent follows our design system. We can only tell you it can no longer break the three specific things above. That is a real improvement and it is not the same as knowing. If you build the measuring part before we do, I would like to hear about it.