{"slug": "linting-a-design-md-before-your-ai-coding-agent-reads-it", "title": "Linting a DESIGN.md before your AI coding agent reads it", "summary": "A developer hand-wrote a DESIGN.md file for a fictional coffee shop site and ran Google's @google/design.md linter (v0.4.0), which flagged one error and four warnings: an unresolved token reference, a missing 'primary' color, a 2.15:1 contrast failure on the badge, an orphaned 'muted' token, and missing typography. After renaming the brand token to 'primary', adding typography, rounded and spacing sections, and a caption component, the file linted clean at 0 errors and 0 warnings, with the developer noting the linter only checks contrast within component color pairs and misses other issues.", "body_md": "DESIGN.md is a Markdown file with YAML front matter that tells an AI coding agent what your UI looks like: colors, type, radii, spacing and a few components. The tokens go in the front matter, and the Markdown body says how to use them. Google publishes the format along with a CLI, `@google/design.md`, that lints it.\n\nI wrote a first draft by hand for Northwind Roasters, a fictional coffee shop site I use for demos, ran the linter (v0.4.0), and fixed what it found. Here is what it caught, and what it doesn't check.\n\nThis is the kind of file you write in five minutes from DevTools:\n\n```\n---\nversion: alpha\nname: \"Northwind Roasters\"\ncolors:\n  brand: \"#c2410c\"\n  amber: \"#f59e0b\"\n  background: \"#fffaf3\"\n  surface: \"#f6eadb\"\n  text: \"#2b1d14\"\n  muted: \"#7a6656\"\ncomponents:\n  button-primary:\n    backgroundColor: \"{colors.brand}\"\n    textColor: \"#ffffff\"\n  badge-new:\n    backgroundColor: \"{colors.amber}\"\n    textColor: \"#ffffff\"\n  card:\n    backgroundColor: \"{colors.surfce}\"\n    textColor: \"{colors.text}\"\n---\n```\n\nComponents point at tokens with `{colors.name}` references instead of repeating hex values, so the agent knows the card background *is* the surface color rather than a near-identical one.\n\n```\nnpx @google/design.md lint DESIGN.md\n```\n\nThe summary: 1 error, 4 warnings, 3 infos. It exits with code 1, so you can fail a CI step on it. The findings, shortened:\n\n| Rule | Severity | Message | \n|---|---|---|\n| broken-ref | error | Reference `{colors.surfce}` does not resolve to any defined token. | \n| missing-primary | warning | No 'primary' color defined. The agent will auto-generate key colors… | \n| contrast-ratio | warning | textColor (#ffffff) on backgroundColor (#f59e0b) has contrast ratio 2.15:1, below WCAG AA minimum of 4.5:1. | \n| orphaned-tokens | warning | 'muted' is defined but never referenced by any component. | \n| missing-typography | warning | No typography tokens defined. Agents will use default font choices… | \n| missing-sections | info | No 'spacing' section defined. (and the same for 'rounded') | \n\nEach one in turn:\n\n`brand`. The format expects a color named `primary`, and without one the agent picks its own key colors. Renaming the token was the whole fix.`#2b1d14` on the same amber is 7.59:1.`muted` is my caption color, but no component used it. An unused token is just a hex value with no instructions, so I added a `caption` component.\n\n```\n---\nversion: alpha\nname: \"Northwind Roasters\"\ncolors:\n  primary: \"#c2410c\"\n  amber: \"#f59e0b\"\n  background: \"#fffaf3\"\n  surface: \"#f6eadb\"\n  text: \"#2b1d14\"\n  muted: \"#7a6656\"\ntypography:\n  headline-display:\n    fontFamily: \"Fraunces\"\n    fontSize: 64px\n    fontWeight: 600\n    lineHeight: 1.04\n    letterSpacing: -1.28px\n  body-md:\n    fontFamily: \"Hanken Grotesk\"\n    fontSize: 17px\n    fontWeight: 400\n    lineHeight: 1.6\nrounded:\n  lg: 10px\nspacing:\n  base: 4px\ncomponents:\n  page:\n    backgroundColor: \"{colors.background}\"\n    textColor: \"{colors.text}\"\n  button-primary:\n    backgroundColor: \"{colors.primary}\"\n    textColor: \"#ffffff\"\n    rounded: \"{rounded.lg}\"\n  badge-new:\n    backgroundColor: \"{colors.amber}\"\n    textColor: \"{colors.text}\"\n  card:\n    backgroundColor: \"{colors.surface}\"\n    textColor: \"{colors.text}\"\n  caption:\n    textColor: \"{colors.muted}\"\n---\n```\n\nLint result: 0 errors, 0 warnings, 1 info (the token summary). White on `#c2410c` for the button is 5.18:1, so that pair passes as well.\n\n`design.md diff old.md new.md` gives a JSON report of what changed: `brand` removed, `primary` added, two typography tokens added, `page` and `caption` added, three components modified. It also shows the finding counts before and after (errors -1, warnings -4). That's handy for a PR description when someone edits the file.\n\nZero warnings doesn't mean the file is right. Three things I checked by hand:\n\n**Contrast is only checked inside a component.** The rule looks at a component's own `backgroundColor`/` textColor` pair. My `caption` component only has a text color, so nothing checked it. On the page background, muted is 5.24:1. On a card it is 4.59:1, which still passes, but only just. If your agent puts captions on cards, that's the pair to check yourself.\n\n**The values have to be the real ones.** The linter can't know that the body text is 17px and not 16px, or that the h1 is tracked to -1.28px. Copy those from DevTools Computed on the real site, not from memory.\n\n**Exports drop some things.** `design.md export --format css-tailwind` turns the file into a Tailwind v4 `@theme` block. For this file it wrote colors, font families, sizes, tracking, weights, radius and spacing, but in v0.4.0 the `lineHeight` values didn't appear in the output, and the font families came out without fallbacks (`--font-body-md: \"Hanken Grotesk\";`). Add those by hand if you use the export.\n\n`{colors.x}`, and give every color at least one component that uses it.` npx @google/design.md lint` in CI, then check contrast on cards and the real values by hand.\nI made a Chrome extension, Stylasso, that writes a DESIGN.md from the page you're on: real colors with roles, fonts, type scale, radii and spacing, with components referencing the tokens. The three demo-site files it generates lint with 0 errors; one gets the same white-on-violet contrast warning I'd expect, because that's what the site really uses. There's a short guide here: [DESIGN.md from any website](https://stylasso.pixlasso.com/design-md?src=devto). Writing the file by hand, as above, works too.\n\n*Written with help from an AI assistant. Every lint result, diff and export above comes from running @google/design.md v0.4.0 on the files shown.*", "url": "https://wpnews.pro/news/linting-a-design-md-before-your-ai-coding-agent-reads-it", "canonical_source": "https://dev.to/casper_tran_59c6a8b1e1f3d/linting-a-designmd-before-your-ai-coding-agent-reads-it-54n", "published_at": "2026-11-12 04:45:27+00:00", "updated_at": "2026-11-12 04:50:41.953407+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "developer-tools", "generative-ai"], "entities": ["Google", "Northwind Roasters", "Fraunces", "Hanken Grotesk"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/linting-a-design-md-before-your-ai-coding-agent-reads-it", "markdown": "https://wpnews.pro/news/linting-a-design-md-before-your-ai-coding-agent-reads-it.md", "text": "https://wpnews.pro/news/linting-a-design-md-before-your-ai-coding-agent-reads-it.txt", "jsonld": "https://wpnews.pro/news/linting-a-design-md-before-your-ai-coding-agent-reads-it.jsonld"}}