Free playbooks in your inbox

The Conventions File That Makes Claude Design Use Your Design System

A conventions file built from the CSS you already ship makes every generated component come out in your own design system instead of a generic one. The file Claude Design reads is `.design-sync/conventions.md`, not the `DESIGN.md` everybody writes, and token names one word off your stylesheet are worse than no file at all.

From the youcanbuildthings catalog ▸ Build-tested

Hello builders,

A conventions file built from the CSS you already ship is what makes every generated component come out in your design system rather than a generic one. Getting a claude design system as code into the loop starts with a correction, because the file everybody reaches for does not exist: there is no DESIGN.md, Claude Design has never read one, and searching the tool for it turns up nothing at all.

That’s not a pedantic point, it is the reason a lot of people conclude the whole feature is broken. We write a DESIGN.md, we commit it, we sync, and nothing happens. No error, no warning, the file just sits there. So we decide our instructions were not clear enough and go back to prompting harder at a canvas that never received a single word we wrote.

DESIGN.md is a community convention, and a good one. Somebody built a tool where you paste a URL and get back a generated DESIGN.md describing that site’s palette, type scale and components. Somebody else built an entire brand system out of one markdown file, and neither of them is lying to you. They are describing a file any model will happily read if you paste it into the conversation, because models read whatever you put in front of them. The leap that got made was from “a markdown file describing your design system is useful to a model” to “Claude Design reads a file called DESIGN.md,” and the second one is invented.

The file it actually reads

The real file is .design-sync/conventions.md, and you write it yourself, commit it, and wire it up in your config:

{ "readmeHeader": ".design-sync/conventions.md" }

Then rebuild, because it gets stitched into the generated README at build time, which means an unbuilt change is not a change.

Before writing a line of it, know who reads it. It is not a colleague who can go and look something up when the prose gets vague. It is a model that builds apps with your component library, hundreds of times, for people who will never see this file, and it will not run your build or read your source. It gets the README and the bound artifacts and nothing else.

That gives us the one test every sentence has to pass: could the design agent act on this without guessing? “Follow the design system’s conventions” fails that test. Delete it and write the convention.

The reason is mechanical. An agent in that position follows concrete, enumerated guidance and cannot follow guidance that isn’t there. Name the tokens and it uses tokens. Leave the class vocabulary unnamed and it will not guess at yours, it will invent its own, because a model cannot leave a blank. That is the mechanism behind every “the AI ignored my design system” complaint you have read.

One word apart

Now the failure that costs you, which is worse than writing nothing at all.

Three panels headed "A conventions file that lies is worse than none." Panel one, "tokens say one thing", shows .design-sync/conventions.md with a table of gap-sm 0.5rem, gap-md 1rem circled in pink, and gap-lg 1.5rem, and below it dist/styles.css declaring --gap-small 0.5rem, --gap-medium 1rem circled in green, and --gap-large 1.5rem, joined by a red X reading "one word apart". Panel two is an arrow reading "every card built from now on". Panel three, "the result", is five identical product cards with a callout reading "no error, anywhere" and the caption "Not a wrong gap. No gap." A terminal strip at the bottom reads NOT IN STYLESHEET: --gap-md.

I wrote gap-md in the table, because gap-md is what I call it in my head and have called it in conversation for months:

| token  | value  |
|--------|--------|
| gap-sm | 0.5rem |
| gap-md | 1rem   |
| gap-lg | 1.5rem |

The compiled stylesheet declares something else:

:root {
  --gap-small:  0.5rem;
  --gap-medium: 1rem;
  --gap-large:  1.5rem;
}

One word apart, and now the most authoritative document in the agent’s context tells it, with total confidence, to use a variable that doesn’t exist, so it does. Every card built from that moment on has a gap that resolves to nothing. Not a wrong gap. No gap. And no error anywhere, because CSS does not owe us an exception for a variable we never declared. That file did not fail to help the agent, it poisoned it, on every screen, with more authority than any prompt of mine ever had.

So there is a mandatory pass before you commit, and it is mechanical:

grep -o 'var(--[a-z0-9-]*)' .design-sync/conventions.md | sort -u \
  | while read -r v; do
      n=${v#var(--}; n=${n%)}
      grep -q -- "--$n" dist/styles.css || echo "NOT IN STYLESHEET: --$n"
    done
NOT IN STYLESHEET: --gap-md

Re-run that against every fresh build, not once. Your components get refactored over time and the conventions file does not notice.

Go look at what you already ship

One more command, because the sync ships design docs you did not choose. Its config carries a glob that is on by default:

guidelinesGlob    string or string[] of design-guideline .md files to copy
                  into guidelines/.
                  Default: ['docs/guides/**/*.md', 'docs/*.md', 'guides/**/*.md']

Anything matching gets copied into a guidelines/ folder, uploaded with your bundle, and handed to the design agent as the design system’s own usage guidance. Mine had three files in docs/: a real styling guide, a migration note from a refactor that finished long ago, and a stub with a heading and two sentences under it. All three shipped, and the agent read all three with equal seriousness, because it has no way of knowing that one of them is a ghost.

Either fix the ghosts or narrow the glob:

{ "guidelinesGlob": ["docs/design/**/*.md"] }

One rung further, if you want it

If you want your tokens to be portable data instead of CSS, there is a format for it, and two things are worth getting right before you cite it to a client. It is the Design Tokens Format Module, version 2025.10, and it is a Final Community Group Report, which is not a W3C Standard and is not on the W3C Standards Track. Say it that way, because the people who care about that distinction care a great deal.

What makes it mechanically checkable is one rule:

“An object with a $value property is a token… A group is identified as a JSON object that does NOT contain a $value property.”

So brand below is a group and brand.primary is a token, and nothing else about the file needs explaining:

{
  "brand": {
    "$type": "color",
    "primary": {
      "$value": { "colorSpace": "srgb", "components": [0, 0.4, 0.8] }
    }
  }
}

Two naming rules travel with it. Names must not begin with $, and “…due to the syntax used for token aliases the following characters MUST NOT be used anywhere in a token or group name: { (left curly bracket), } (right curly bracket), . (period).” (Design Tokens Format Module 2025.10)

One trap on the way. The tr.designtokens.org address most links point at redirects to a working draft banner-marked “Do not reference this version as authoritative in any way,” so link the versioned URL.

Now go build something this weekend!

John Cook

Why trust this? Every youcanbuildthings guide is pulled from a build-tested book: code that ran in production before it was written down.