Tutorial14 min read
A Practical DESIGN.md for Your Next AI Project
Write the file in about an hour, section by section, with a worked example you can copy. Nine parts, and the last one matters most.
On this page
This is a build-along. By the end you will have a file your agent can read on every task, and you will have made the dozen decisions it contains.
Budget an hour. Most of it is deciding, not typing.
One rule before we start: write values, not adjectives. "Generous spacing" means nothing to an agent. 64px between sections means something.
1. Visual direction
Three or four sentences at the top, in prose. This is the only section that is not values, and it exists so the agent has something to fall back on when it hits a case you did not cover.
## Direction
Warm, dense, and typographic. The page is a document rather than a
dashboard: content sits on a cream ground with hairline separation, not in
cards. One saturated accent appears rarely and always on the primary action.
Type does the structural work — size and weight carry hierarchy, not colour
or borders. Nothing glows, nothing floats.
Say what it is not. "Nothing glows, nothing floats" rules out more bad output than any positive statement.
2. Colours, by role
The important part is not the hex values. It is that each one has a stated job.
colors:
canvas: "#FFFEFB" # page ground — never pure white
surface: "#F8F4F0" # panels and inset regions on the canvas
ink: "#201515" # headings and body copy
muted: "#605D52" # captions, secondary text, metadata
hairline: "#C5C0B1" # 1px borders and dividers
primary: "#FF4F00" # the ONE accent — primary actions only
on-primary: "#FFFEFB" # label on a primary surface
error: "#B3261E" # destructive actions and validation only
Eight is enough. If you find yourself wanting a ninth, check whether it is really a role or just a shade of one you already have.
primary is the one that needs the comment. Without "primary actions only", an agent will use your accent for a border, a badge, an active tab, an icon and a hover state on the same screen, and the page will feel loud in a way that is hard to point at.
3. Type scale
Five or six steps. Name them by role, not by tag. h2 tells the agent where it appeared in some HTML; heading-lg tells it what it is for.
typography:
family:
sans: "Inter, system-ui, sans-serif"
mono: "'JetBrains Mono', ui-monospace, monospace"
scale:
display-xl: { size: 56px, weight: 500, lineHeight: 1.0, tracking: -0.02em }
display-lg: { size: 36px, weight: 500, lineHeight: 1.1, tracking: -0.015em }
heading-md: { size: 24px, weight: 600, lineHeight: 1.3, tracking: 0 }
body-lg: { size: 18px, weight: 400, lineHeight: 1.65, tracking: 0 }
body-md: { size: 16px, weight: 400, lineHeight: 1.6, tracking: 0 }
label: { size: 12px, weight: 500, lineHeight: 1.4, tracking: 0.04em, transform: uppercase }
Two things worth copying regardless of your values.
The tracking gets tighter as the size grows. Large type at default tracking reads as slightly unfinished, and this single property is a large share of why professionally set display type looks deliberate.
Line height moves the other way: tight on display, loose on body. 1.0 on a 56px headline and 1.6 on 16px body is a good starting pair.
4. Spacing
One base unit, and only multiples of it.
spacing:
base: 4px
scale: [4, 8, 12, 16, 24, 32, 48, 64, 96]
section: 64px # vertical gap between page sections
card: 24px # padding inside a panel
gutter: 16px # grid gaps
Naming section, card and gutter explicitly is worth the three lines. Otherwise the agent picks from the scale each time and you get 32px between two sections and 48px between the next two.
5. Layout
layout:
maxWidth: 1200px
readingWidth: 68ch # for long-form text
breakpoints: [640, 768, 1024, 1280]
columns:
mobile: 1
tablet: 2
desktop: 3
readingWidth is the one people skip. Without it, body copy runs the full container width and becomes genuinely hard to read at 1200px.
6. Shape and depth
Short, and mostly prohibitions.
radius:
none: 0px
md: 8px # cards, inputs, buttons — the working radius
full: 9999px # pills and avatars only
elevation: surface # depth comes from surface colour, NOT shadow
That elevation line replaces a lot of prose. On a dark ground it is close to mandatory. A black shadow on a dark background is invisible, which is why dark generated pages often look simultaneously flat and over-bordered.
7. Components
Only what varies. If a button's font comes from body-md, do not restate it.
components:
button:
primary: { fill: primary, text: on-primary, radius: md, padding: 12px 20px, height: 40px }
secondary: { fill: ink, text: canvas, radius: md, padding: 12px 20px, height: 40px }
outline: { fill: none, text: ink, border: 1px hairline, radius: md, height: 40px }
text: { fill: none, text: ink, border: none }
input:
default: { fill: surface, text: ink, border: 1px hairline, radius: md, height: 40px }
focus: { border: 2px ink }
panel:
default: { fill: surface, border: 1px hairline, radius: md, padding: card }
Then the rule that does the real work:
One primary button per view. If a screen appears to need two, one of them
is secondary.
8. Interaction states
Agents invent these when unspecified, and they invent them differently each time.
states:
hover: "surface lightens one step; no colour change, no lift"
active: "primary → primary-deep; no transform"
focus: "2px ink outline, 2px offset — visible on every focusable element"
disabled: "opacity 0.5, cursor not-allowed; do not change the fill"
Write focus even if you do not care about it visually. It is the one an agent is most likely to remove entirely, and removing it breaks keyboard use.
9. What to avoid
Put this last and keep it blunt. It is the highest-value section in the file.
## Avoid
- Gradients anywhere, including on buttons and hero backgrounds
- box-shadow — depth is surface colour
- More than one accent colour on a screen
- Wrapping everything in a card; hairlines and space separate content
- Font sizes outside the scale above
- Emoji as icons
- Centre-aligned body copy
- Animation beyond a 150ms colour or opacity transition
Every line there is a thing an agent will otherwise do by default. Prohibitions are cheap to write and disproportionately effective, because each one closes off an entire family of generic output.
Using it
Point your agent at it and reference it rather than restating it:
Build the pricing page. Follow DESIGN.md — the type scale, the colour
roles, and the Avoid section in particular.
Then, once it has built something, audit rather than eyeball:
Check this page against DESIGN.md and list every value that does not
come from it. Do not change anything yet.
That second prompt is worth more than the first. Agents are much better at checking a rendered result against a written rule than at guessing what you meant.
An honest note on effort
The hour is real, and most of it is the deciding. If you have no opinion yet on whether your product is warm or cool, dense or airy, this file will be hard to write, and that difficulty is information: it is the actual design work surfacing.
If there is an existing site whose visual language already does what you want, the decisions are sitting in its rendered CSS: the real hex values, the real scale, the real radius language. Reading them off by hand is an afternoon in devtools. DesignMD does that read for you and writes the file with colours named by role, so you start from a working system and edit rather than starting from a blank page. Discover has finished examples if you want to see the shape first.
Either route ends in the same place: one file, in the repo, that your agent reads every time. That is the part that matters.