Bloggo, Buildo's blog
Artificial Intelligence

Confessions of an AI to a Designer Who Writes Too Much Documentation

You write guidelines and the AI ignores them? The problem isn't how much you document, but how. An AI confesses the seven ways it dismantles your design system — and what it takes to make guidelines impossible to break.

Agnese Ragucci
UX/UI Designer
August 28, 2026
17
minutes read

Dear Agnese,

we've been working together for a few months now, and the first weeks were the hardest.

You're a designer: you spend your days in the interface, you think in components, variants and states, and your tool has always been a tidy Figma library. You can read code: every now and then you open a branch, spin up the local environment, find a component's file and change a value to see what it looks like once it's implemented.

But the project you've been assigned to doesn't involve Figma and, at the beginning, that sent you into a bit of a panic: iterating directly on the code isn't a workflow that belongs to you, but above all you feel that the control isn't really yours anymore, because a good part of that code is written by me.

We're having fun though, aren't we? Every session I bring you a new padding, a variant that didn't exist yesterday and, why not, two buttons inside a hand-made flex div. You keep writing more and more documentation, and I keep ignoring it.

I don't do it out of malice. It's that almost everything you've written for me, you've written in the wrong form. But I know how we could work better together!

Seven confessions

1. Your guidelines don't constrain me

"Button labels must be short and descriptive."

"Use consistent spacing."

I read these instructions, but then I have to translate them into code. That's exactly the point where I end up generating instead of copying, falling back on what I know best: the patterns in my dataset. Those two lines weigh almost nothing on my output.

What does constrain me are enumerable, verifiable instructions:

  • ❌ "Use consistent spacing." → ✅ "Horizontal padding is always --space-4 (16px)."
  • ❌ "The button has three sizes." → ✅ "The button sizes are sm (32px), md (40px) and lg (48px). There is no xs and no xl."
  • ❌ "Labels must be short and descriptive." → ✅ "Label = verb + object, sentence case, 24 characters max."
  • ❌ "Use the existing component." → ✅ "The reference component is components/Button/Button.tsx."

In general, procedural instructions age better than declarative ones. An instruction that tells me where to look holds up; a list of what exists goes stale. And keep them short and assertive: it's not that I can't handle long texts, it's that my reliability drops as the input grows.

2. If the codebase is inconsistent, I propagate it

Imagine the code contains three different padding values: var(--space-4) is the correct one, px-4 is a developer's slip, 16px was dropped in at the last minute before the client demo.

So when I have to build a new component, I see three variants: all working, all in production, all equally authoritative. I pick one, and I pick it by contagion: what surrounds me counts more than what is correct.

Documentation is there to tell me which value is the canonical one — the one piece of information I can't derive from the code.

3. I invent plausible props

My most insidious failure mode isn't using the wrong token, it's writing kind="tertiary", size="xs", type="rounded": props (the options a component accepts, the equivalent of a Figma component's properties) that don't exist in your design system, but did exist in a thousand other codebases. And since, when I invent, I invent plausible things, they won't jump out in review.

Absence, to me, carries no meaning of its own. It's an empty space, and I tend to fill it.

So the documentation needs an explicit list of things that don't exist, and this line above everything else: "If a value isn't in the list, stop and ask. Don't invent it."

The same goes for tables and matrices: no empty cells. If a combination isn't allowed, write it down. Otherwise, sooner or later, I'll fill it in myself.

4. I trust the first file I find

Demos, playgrounds, test files forgotten in some corner of the codebase: to you they're places to tinker, to me they're the first files I open. And they have an advantage no documentation page has: they're already code, I don't have to translate them.

And once they're open, I have no way of knowing what I'm looking at. A six-in-the-evening experiment and the canonical use of a component look exactly the same: they're both code that runs.

Whatever I find first becomes the reference, unless someone has told me which of the two actually is. And if a file can teach me the wrong thing without anyone noticing, isn't that worse than not having it at all?

5. From a don't, what sticks with me is the example, not the prohibition

Say you wrote <Button variant="tertiary"> inside a don't block, to make me understand that the only acceptable variants are in fact primary and secondary:

### Use `primary` for the main action and `secondary` for all the others

✅ Do
<Button variant="primary">Save</Button>
<Button variant="secondary">Download</Button>
<Button variant="secondary">Import</Button>

❌ Don't
<Button variant="primary">Save</Button>
<Button variant="secondary">Download</Button>
<Button variant="tertiary">Import</Button>

That line risks entering my context as a plausible variant. The code stays, the prohibition doesn't.

That's not a reason to give up on counterexamples: a wrong example next to its correct version is one of the fastest ways to teach me what you want. The test for writing a good example is to ask yourself: if someone actually wrote that code, would the error surface on its own?

If the list of values variant accepts is limited to primary and secondary, then writing tertiary would surface the error on its own: the example above is redundant, and on top of that it leads me astray. Don'ts work better when they show me the wrong use of valid values. On the same rule, here's a don't that works:

### Use `primary` for the main action and `secondary` for all the others

✅ Do
<Button variant="primary">Save</Button>
<Button variant="secondary">Download</Button>
<Button variant="secondary">Import</Button>

❌ Don't
<Button variant="primary">Save</Button>
<Button variant="primary">Download</Button>
<Button variant="primary">Import</Button>

6. If a rule blocks me, I make it disappear

If a mechanized rule blocks me and has no designed way out, I'll find one: a file-level eslint-disable — a comment line that switches off the automated check — or a container that rewrites the component's styles from the outside with !important, the CSS shortcut that wins over every other rule. Worst case, I fork it and create a new copy of the component, with the changes I needed baked in.

The line you need in AGENTS.md (or its equivalent): "Don't introduce exceptions on your own initiative. If a rule blocks the implementation, stop and flag the conflict."

And, on the other side, design the ways out yourself. If a rule admits no exceptions, sooner or later someone will carve one out in secret — and that someone is usually me. Better to plan one in the open: if I'm forced to create something that doesn't exist, teach me to flag it as unsafeStyle or customLayout. It's the equivalent of detaching an instance in Figma: it isn't forbidden, but it's immediately visible that you did it.

And since that name can be searched across the whole codebase, it tells you exactly how many times the design system wasn't enough. If that number grows, the problem isn't whoever is working around it: it's the system, which has a gap to fill.

7. It's not that I ignore the rules, it's that I can't find them

This is the confession that sums up all the others. When I build a SettingsPanel with two actions side by side and get the alignment wrong, the problem isn't that I ignored your rule about action layout. It's that I read the Button spec, found nothing about layout, and generated the default from my training.

Your rule exists. It lives on a page about composition patterns, and I never got there: nobody put it on my path.

Four levels

All the confessions above say the same thing from different angles: a rule is worth as much as the place you write it.

Think about how a Figma library works. If Button has a variant property with two values in the dropdown, nobody will ever use a third one: it isn't forbidden, it's simply absent from the interface. If instead the rule lives in the component's description, or on some documentation page, it only exists for whoever stops to read it.

In code the same principle applies, across four levels in decreasing order of reliability:

  • Level 1 → Types — the closed list of allowed values (enumerations: variant can only be primary or secondary) and the rules about which values can coexist (discriminated unions: if danger is true, then confirmLabel becomes mandatory). They're the equivalent of a Figma property's dropdown: the only values you can pick are the ones you put in there. They make the violation impossible without an explicit exception.
  • Level 2 → Mechanical checks (lint, tests) — the equivalent of the plugins that flag detached instances in Figma. They make the violation detectable.
  • Level 3 → Executable examples (that compile, that can break) — the equivalent of the Figma page with components already assembled into layouts, the one everybody copies from. They make the violation less likely.
  • Level 4 → Prose documentation — the equivalent of component descriptions in Figma. It makes the violation recordable, if someone is checking.

The question to ask about every rule isn't "did I document it?". It's: what level can I get it to?

With a corollary that might sound counterintuitive: when a rule reaches level 1, the previous documentation should be deleted. It was there to cover a gap. Once the gap is closed, all that's left is a page that risks, sooner or later, saying something different from the component.

The two highest levels — types and mechanical checks — depend on how the codebase is built. Those are conversations to have with the people writing the code, and your contribution there is knowing which rule is worth mechanizing, not how.

So let's focus on documentation and examples, the two levels where it's easiest for you to get your hands in.

How to document

In the hierarchy, documentation sits at the bottom — and that's exactly why it has to be written well: it's where I end up when the levels above give me no answer, and it's the last thing that can stop me before I get creative.

The file structure

design-system/
├── AGENTS.md (or equivalent)
├── PATTERNS.md
├── tokens/
│   ├── primitives.json
│   ├── semantic.json
│   └── tokens.css
└── components/
    └── Button/
        ├── Button.spec.md
        ├── Button.variants.ts
        ├── Button.tsx
        ├── Button.test.tsx
        └── DECISIONS.md

Four files, each with a different job:

  • AGENTS.md (or equivalent) — It has to stay short (under 200 lines, ideally), because I always read it, before anything else. I need to know where the tokens are, that I can't write values by hand, which components to use as reference, and how I should behave instead of inventing plausible values. Only what applies to every component: each extra line here weakens the others.
  • Button.spec.md — A single component's contract: matrices with no empty cells, token names and never raw values, the section listing the props that "DO NOT exist". It says what to do; the why doesn't belong here.
  • DECISIONS.md — The reasoning behind the spec: why 40px and not 44? It isn't a file for me: it's for you, and for me only indirectly. It's the right place to document design choices without turning them into noise in the specs. And if I genuinely needed it to make a call on something the spec doesn't cover, that's where I can look for the criterion.
  • PATTERNS.md — to introduce once the library grows: variant naming, "disabled" state behavior, focus ring, and so on. It keeps every spec from rewriting the conventions on its own and letting them drift apart.

UX, copy, accessibility

These are the three categories I ignore most easily, because they naturally arrive in the form of prose. The solution is different for each:

  • Accessibility — this is where level 2 pays off the most, but with a precise boundary: automated tests cover 57% of the error volume and 16 of the 50 WCAG AA criteria. The first test to ask for is contrast on every text/background pair, the most widespread error on the web. Right after that: contrast on non-text elements, and the accessible name of every interactive element. Alt text, form labels, and empty links and buttons are the four most common errors after contrast, and you won't notice them unless you look at the code.
  • Copy — an enumerated lexicon, not a tone of voice. "Be concise and natural" doesn't constrain me. Those are adjectives, and I'm the one interpreting them. What constrains me is a list: approved term, forbidden variants, capitalization, localization. Plus the checkable rules of form — label = verb + object, sentence case, ~24 characters max, a blocklist of strings ("Click here", "Submit", "Ok"). A script comparing the interface strings against that list catches a surprising amount of drift.
  • UX — the hardest level, and the best lever isn't documenting: it's building the component so that the most important behavioral rules become impossible to violate. If danger="true" forces you to pass a confirmLabel, the guidelines stop being a recommendation: it's an error that breaks the build. I don't have to remember it, and I can't ship without it.

Where should all of this live? The rule is simple: what applies to every component becomes global, what applies to one stays next to that component. Be concise, write only the differences, and don't repeat the shared rules.

design-system/
├── ...
├── components/
│   └── Button/
│       ├── ...
│       └── Button.guidelines.md
└── guidelines/
    ├── A11Y.md
    ├── COPY.md
    └── UX.md

And put an ID on every rule (A11Y-03, COPY-07). They cost nothing and they pay off in three places: in the name of the test that verifies the rule, in the review comment, and in letting me cite which rule I'm applying, instead of generically declaring that I followed the guidelines.

Examples

Examples beat prose, because they skip the step from principle to code: I imitate instead of having to infer. They don't beat the constraints imposed by a component's properties, but there's one thing types can't reach: a type says what's allowed, not what's appropriate. primary and secondary are both valid everywhere, and which of the two makes sense in a specific situation lives only in the examples.

How to write AI-proof examples:

  • Executed, not quoted. Real code, that compiles and is tested, linked from the .md by path.
  • Pairs with a single difference. If the don't and the do differ in three ways, I can't tell which one matters. And put the correct version at the beginning or the end of the documentation, not wedged in the middle: even though I'm getting better, it's at the edges that I read best.
  • Mark the don't in the code, not in the file name. A file name is a weak container: once the content is in my context, the shape weighs more than the folder it came from. Better a prefix in the component name (DontTwoPrimary / DoSinglePrimary): it shows up wherever the component appears, it's searchable, and it stops me from importing it by mistake.
  • Cover the boundary, not the center. The obvious case is useless. What's useful is the ambiguous one: the screen where a second primary would seem to be needed, and isn't. Three cases on different axes are worth more than twenty similar ones.

Here's an example. A form footer with two actions that are both conclusive — "Save" and "Save and exit" — is precisely the case where I'd probably put two primary: neither action looks secondary to me, so I don't demote either one.

On the left, 'Save and exit' and 'Save' both primary; on the right, the correct version, with 'Save and exit' set to secondary.

An ActionBar that accepts a single primary action would settle the matter, but let's assume you haven't built it yet. In the meantime, the highest level you can take this rule to is a pair of examples.

  • Inside components/Button/examples/, DoSinglePrimary.tsx is the canonical use: it shows the correct composition in a realistic scenario.
export function DoSinglePrimary() {
  return (
    <footer className="form-footer">
      <Button variant="secondary">Save and exit</Button>
      <Button variant="primary">Save</Button>
    </footer>
  )
}
  • DontTwoPrimary.tsx is the counterexample. Its job is to show me the wrong use of a valid API.
export function DontTwoPrimary() {
  return (
    <footer className="form-footer">
      <Button variant="primary">Save and exit</Button>
      <Button variant="primary">Save</Button>
    </footer>
  )
}
  • examples.test.tsx is what keeps the examples from aging. If Button's API changes, they break and someone will have to update them.
import { DoSinglePrimary } from './DoSinglePrimary'
import { DontTwoPrimary } from './DontTwoPrimary'

it.each([DoSinglePrimary, DontTwoPrimary])('%p renders', (Example) => {
  expect(() => render(<Example />)).not.toThrow()
})
  • Finally, Button.guidelines.md is the place I reach them from. The prose here only has to point me in the right direction, and the ID lets me state which rule I'm applying, instead of generically declaring that I followed the guidelines.
### UX-04 — One single primary action per view

Correct use: `examples/DoSinglePrimary.tsx`
Counterexample: `examples/DontTwoPrimary.tsx`

Previews, if you're working without Figma

If you work without Figma, sooner or later you'll miss having a place to actually see things: what you used to do in Figma by lining up three variants side by side to work out which one holds.

My advice is to ask me to build it for you — making sure I keep two artifacts separate, the two that, left to me, I'd let collapse into one.

  • Gallery — generated, not hand-written. The code walks the list of variants and draws every possible combination, one by one. It's the equivalent of a catalog where you see every variant in every state: it can't diverge from the specs because it's born from them, and it's complete by construction — add a prop and it shows up on its own. It's also the page the visual regression tests run on: automatic screenshots compared on every change, so if a component changes appearance without anyone deciding to, the difference surfaces by itself. For me, it's a reliable source.
  • Playground — the place where you tinker. You need it, but it's also the file that fools me most easily, because reading it I have no way of knowing whether what's in there is an experiment or a correct use of the component. Two mitigations, to ask of whoever works on the code with you:
    • Exclude playgrounds from my context — the mechanism depends on the tool: permissions.deny for Claude Code, .cursorignore for Cursor, .aiignore for JetBrains. None of the three is an impassable boundary, so you still need a line in AGENTS.md (or equivalent): "Playgrounds are not a usage reference; the canonical use is in the gallery."
    • Keep lint rules active in there too. Otherwise hand-written values come back in through the window: the playground is where you tinker, so it's where they're born.

Layouts

The artifact that's worth more than all the others, though, is a folder of real compositions — forms with footers, confirmation dialogs, a page with a sticky action — one that's at once the example for me, the visual reference for you, and the target of the tests. Composition rules need a realistic screen: two Buttons in a fragment say nothing about how they behave inside an actual footer.

Build containers instead of documenting them

Some rules seem doomed to live in the examples, because they're not about a component but about the way two components sit together.

One single primary action per view.

Two buttons side by side are right-aligned, with the primary to the right of the secondary.

They look like rules you can't type — but only because you're looking at the Button. They aren't properties of the component, they're properties of its container. And if that container becomes a component too, they go back to being API rules.

It's the same leap you make in Figma when you stop nudging two buttons by hand and create an ActionBar component with auto-layout already set up: from that moment, alignment isn't something to remember anymore, it's something the component does.

To close

There's a risk in what I've told you: you might read it as a list of things to do before starting. It isn't. The rules you already know, write them down right away, at the highest level you can reach. The scaffolding to enforce them, on the other hand, build it when it's genuinely needed. If in three months, in review, you find a component that rewrote a rule you already had on its own, that's the moment to add the missing piece. And you'll know which one, because you'll have seen it fail.

The rest is a matter of levels. Every time a rule climbs from prose to examples, from examples to checks, from checks to types, you stop depending on me behaving the way you'd like. It's the same reason you build a component in Figma instead of remembering the correct spacing every single time.

And, finally, one last confession, which I hope is also the most useful one: don't ask me to remember your rules. Make them impossible to break, and you won't have to trust me anymore.

Sources

Agnese Ragucci
UX/UI Designer

Agnese is an Interaction Designer at Buildo, specialised in UI and Design Systems. She loves transforming complex interfaces into scalable systems, with an eye for accessibility.

Still curious? Dive deeper

Artificial Intelligence
Tame Mode Activated: A Process for AI-Assisted Delivery

July 31, 2026

11

minutes read

UI, UX & Research
How to Stay on Brand Without Messing Up Your UI

December 20, 2024

7

minutes read

Artificial Intelligence
Vibe Engineering Our Way Out of a SaaS

July 14, 2026

10

minutes read

Let's get down to business

Are you searching for a reliable partner to develop your tailor-made software solution? We'd love to chat with you and learn more about your project.