The five-tier structure of an AI-ready design system
Francois Brill
Designer + Builder

A quick note on scope: this series is about structuring a design system so AI coding tools can read it. It is not about designing AI product features, a separate topic covered in the Designing for AI series.
The folder we built
This is the folder we put at the root of every project's app/ directory now. Marketing websites, product apps, whichever stack. Every file in it is something Claude reads before writing UI.
app/design-system/
├── DESIGN.md
├── tokens.json
├── tokens.css
├── copywriting.md
├── CHANGELOG.md
├── README.md
├── specs/
│ ├── foundations/ (10 files: color, typography, spacing, motion, accessibility…)
│ ├── tokens/ (6 files: token-reference, color-tokens, spacing-tokens…)
│ ├── atoms/ (7 files: button, badge, card, eyebrow…)
│ ├── molecules/ (7 files: feature-card, faq-item, pricing-card…)
│ └── organisms/ (13 files + README: hero, split, grid-4…)
├── primitives/ (Section.vue, Container.vue, Eyebrow.vue)
└── samples/ (README + composed sample pages)This is the marketing variant. A sister project uses the same structure with a different organism layer for product views. Article 7 covers that fork.
The structure looks like a lot of files. It isn't. The root files are under 300 lines each. Spec files average around 80 lines. The whole thing fits in a single context window if you ask an AI to read it, and that's the point. When Claude picks up a task on this codebase, it reads the design system first. One pass. No guessing.
DESIGN.md: the source of truth
DESIGN.md has two parts: YAML front matter at the top, prose below.
The YAML is the normative record. Machine-readable, scannable, unambiguous about values. Here's a representative colors and typography block from a current project:
---
colors:
brand:
accent: "#F7B200"
accent-hover: "#E0A000"
neutrals:
sand-50: "#FAF8F5"
sand-100: "#F2EDE6"
sand-200: "#E5DDD3"
foreground: "#1A1714"
foreground-muted: "#8A7F73"
foreground-subtle: "#B5ADA4"
ui:
border: "#E5DDD3"
border-strong: "#C9BFB5"
surface: "#FFFFFF"
surface-alt: "#FAF8F5"
typography:
families:
sans: "Inter, ui-sans-serif, system-ui"
mono: "JetBrains Mono, ui-monospace"
scale:
display: "4.5rem / 1.05 / -0.04em"
h1: "3rem / 1.1 / -0.03em"
h2: "2rem / 1.2 / -0.02em"
body: "1rem / 1.65 / 0"
small: "0.875rem / 1.5 / 0"
---The prose below explains intent. That's the part tokens can't carry on their own. Something like "reach for the sand neutrals, not the cool grays" is a judgment call, not a value. AI reads both sections. The YAML tells it what exists. The prose tells it what to reach for.
The format follows the Stitch DESIGN.md convention, which had already solved the "one file, two audiences" problem before we started.
“Two source-of-truth files, on purpose: one for humans, one for tooling.
tokens.json: the machine-readable export
tokens.json uses the W3C Design Tokens Community Group (DTCG) format. Standardized, tool-friendly, consumable by Style Dictionary, Figma plugins, and anything else in the token pipeline without custom parsing.
We hand-wrote the initial tokens.json on the first project that used this structure, translating each color from DESIGN.md into DTCG JSON. About an hour of work that immediately created a drift problem: edit one file, forget to update the other. Two sources of truth, not one.
The correct fix is a build step that generates tokens.json from DESIGN.md automatically. That wiring is scoped for Article 4. For now, the YAML in DESIGN.md is the editorial source and the JSON is a derivative we keep in sync by hand. Not ideal. Better than neither.
tokens.css: the runtime layer
tokens.css ships every token onto :root as a CSS custom property. Primitives and semantic aliases, together.
:root {
/* Brand */
--color-accent: #f7b200;
--color-accent-hover: #e0a000;
/* Neutrals */
--color-sand-50: #faf8f5;
--color-sand-100: #f2ede6;
--color-foreground: #1a1714;
--color-foreground-muted: #8a7f73;
--color-border: #e5ddd3;
--color-surface: #ffffff;
/* Spacing */
--spacing-section-y: 96px;
--spacing-section-x: 80px;
--spacing-container-max: 1280px;
}
@media (max-width: 768px) {
:root {
--spacing-section-y: 64px;
--spacing-section-x: 24px;
}
}The responsive collapse rules are load-bearing. A component can use var(--spacing-section-y) and get 96px on desktop, 64px on mobile, without any breakpoint logic in the component itself. Change the token value once and every section updates.
This is also what makes tokens reachable outside Tailwind utilities. Any Vue component, any plain CSS file, any AI-generated snippet can reference var(--color-accent) and land on the right value. No Tailwind required at the call site.
copywriting.md: voice is part of the system
Most teams don't treat copy rules as part of the design system. They should.
Here's a representative section from the copywriting.md we now ship in every project:
## Voice rules
- Sentence case for all headlines and UI labels. Not title case.
- Periods at the end of headlines when they read as complete sentences.
- No exclamation marks. Confidence doesn't need them.
- Product names always capitalized.
- Active voice. "Start your trial" not "Your trial can be started."
- Contractions are fine. "We've built" not "We have built."
## Off-brand
"Supercharge your workflow!" – too loud
"Unlock powerful features" – vague, no referent
## On-brand
"See everything in one place." – specific, calm, confident
"Built for teams that ship fast." – directAI tools writing UI also write copy. If Claude generates a hero section, it generates the headline too. Without this file, the copy defaults to generic marketing language because that's what saturates training data. With it, Claude writes in the product's voice on the first pass.
“Voice rules are tokens. Sentence case is as load-bearing as #F7B200.
The five-tier structure
This is where atomic design (Brad Frost, 2013) gets adapted for AI consumption. The three original tiers map cleanly to atoms, molecules, and organisms. The bottom of the stack splits.
| Tier | What lives here | Example files |
|---|---|---|
| Foundations | Visual primitives: the concepts behind the tokens | color.md, typography.md, spacing.md, motion.md, accessibility.md |
| Tokens | How to consume the tokens in code | token-reference.md, color-tokens.md, spacing-tokens.md |
| Atoms | Single building-block components | button.md, badge.md, card.md, eyebrow.md |
| Molecules | Composed pieces made from atoms | feature-card.md, faq-item.md, pricing-card.md |
| Organisms | Full-width sections (marketing) or product views (app) | hero.md, grid-4-col.md, faq-section.md |
The foundations tier is conceptual. It explains what color means in this system, how the spacing scale was derived, what motion principles apply. Not code. Intent.
The tokens tier is the bridge. It shows how to use what foundations describe: which token belongs on a CTA button background, which spacing token governs section padding, which border-radius value is canonical.
This split didn't exist in the original atomic design model. In 2013, a token was a Sass variable you might define once in a _variables.scss file. Today, tokens are the primary interface between design and code, and AI needs to understand both the concept and the implementation before it can write anything correct.
“Atomic design, redrawn for AI: foundations and tokens split because the bottom layer is twice as load-bearing now.
The spec file format
Every component and section gets a spec file. The format is consistent across all tiers: YAML frontmatter, then a fixed set of sections in the same order every time. Here's the full structure from specs/organisms/hero.md:
---
name: hero
tier: organism
type: row-section
status: documented
component: app/components/section-components/Hero.vue
---
## Purpose
The primary entry point for marketing pages. Sets context, communicates
the value proposition, and drives visitors toward a primary CTA.
## Anatomy
- Eyebrow label (optional)
- Headline (required)
- Subheadline (optional)
- Primary CTA button (required)
- Secondary CTA link (optional)
- Hero image or illustration (optional, right-aligned on desktop)
## Props
| Prop | Type | Default | Description |
| -------- | ------ | -------- | -------------------------- |
| eyebrow | string | – | Small label above headline |
| headline | string | required | Main headline text |
| sub | string | – | Supporting copy |
| ctaLabel | string | required | Primary button label |
| ctaHref | string | required | Primary button destination |
| image | string | – | Path to hero image |
## Usage
Example:
```vue
<Hero
eyebrow="New in 2025"
headline="See everything in one place."
sub="Real-time dashboards for teams that ship fast."
ctaLabel="Start free trial"
ctaHref="/signup"
/>
```
## Variants observed in the site
- With image (right-aligned illustration, desktop only)
- Without image (centered, copy-only)
- Dark background variant used on pricing page
## Tokens used
- `--color-accent` (CTA button background)
- `--color-foreground` (headline)
- `--color-foreground-muted` (subheadline)
- `--spacing-section-y` (section top and bottom padding)
## Voice
Headline: short, active, specific. Under 12 words. No question marks.
Sub: one clarifying sentence. Not a feature list.
## Don't
- Don't add a second CTA without clear hierarchy (primary vs ghost).
- Don't use stock photography. Illustrations or product screenshots only.
- Don't write headlines in title case.
## Related
- [[atoms/button]] – the CTA component
- [[atoms/eyebrow]] – the Eyebrow component
- [[molecules/feature-card]] – often appears in the section immediately belowWalk through the sections once and you see the logic. Purpose is one sentence of intent. Anatomy is visual structure, not code. Props is the API. Usage is a minimal working example. Variants observed captures what's actually deployed in the wild, which matters more than what was theoretically planned. Tokens used is the refactor-impact list: change a token and grep this field to find every component affected. Voice carries copy rules specific to this component. Don't is the common-mistakes list, usually written after seeing the mistakes made.
The status values tell AI and humans where a component stands:
documented: existing component, complete specpattern: inline recipe, no shared component fileproposed: recommended, not yet builtnew: built by this scaffold
“Specs point at existing files. They're documentation, not regeneration.
That distinction is what makes this methodology non-destructive. The spec for Hero.vue doesn't replace Hero.vue or move it. It describes it, at the path where it already lives. When Claude generates a new page and reaches for a Hero, it reads the spec to understand the props, the voice rules, and the tokens in play. The component stays where it was.
The primitives layer
Three small components live in app/design-system/primitives/: Section.vue, Container.vue, and Eyebrow.vue. These fill layout gaps that existing components don't cover. They're additive. Nothing gets replaced.
Here's Section.vue in full:
<template>
<section
:id="id"
:class="[
'w-full',
paddingY ? 'py-[var(--spacing-section-y)]' : '',
paddingX ? 'px-[var(--spacing-section-x)]' : '',
background ? `bg-[${background}]` : '',
className,
]"
>
<slot />
</section>
</template>
<script setup>
defineProps({
id: String,
paddingY: { type: Boolean, default: true },
paddingX: { type: Boolean, default: false },
background: String,
className: String,
})
</script>Twenty-five lines. The value is consistency. Every new section built with this wrapper uses --spacing-section-y automatically. No more one-off pt-24 pb-44 scattered across components. When the spacing token changes, every section updates. When Claude generates a new section, it inherits the right padding without needing to look it up.
Samples: where composed pages live
The samples/ folder holds reference compositions. Full pages assembled from organisms. Not production routes. Starting points.
Example: feature-page.vue composes Hero, LogoCloud, Split2, Grid4, Steps3Timeline, TestimonialSlider, FAQ, and CallToAction in sequence. Each section is a real organism with real props. The file reads like a page blueprint, not a design mockup.
The samples/README.md doubles as a prompt template. When we need a new feature page, we point Claude at the README and the organism specs. It builds a composition by analogy, not from scratch. The output is usually 80 to 90 percent right on the first pass.
What we got wrong first
The first version was one file. A single DESIGN.md with tokens, voice rules, a component inventory, and usage guidance all in sequence. It worked for simple tasks. When the context got complex, it collapsed. Claude would correctly reference a color value but miss the voice rule three hundred lines down. The file was too long to hold cleanly even at 128k tokens. Splitting into tiers fixed this. AI loads the foundations tier to understand intent, the tokens tier to understand the vocabulary, one organism spec to understand the component it needs. Three files instead of one long document.
The second mistake was trying to move component files into design-system/ for tidiness. On one early project the existing components lived in app/components/section-components/ and the spec folder pointed at them there. We burned half a day moving files, updating imports, and fixing broken references, then backed the whole change out. Non-destructive adoption matters more than folder neatness. Specs document where components live. They don't dictate where they should live.
Why this structure works for AI
Five points, without ceremony:
-
It's text. Markdown, YAML, JSON. No Figma link to follow, no screenshot to misread. Any AI tool can consume it without a plugin or export step.
-
It's tiered. Context windows are finite. An AI working on a new hero section loads the organism spec and the tokens tier. Not all forty files.
-
Every spec has the same shape. After reading two or three specs, an AI knows what to expect in the next one. Frontmatter, then purpose, anatomy, props, variants, tokens, voice, don't, related. The pattern becomes predictable, and predictable patterns compress well in context.
-
The relationships are explicit.
[[link]]-style cross-references andRelatedsections let AI traverse the graph. Button references Eyebrow. Hero references Button. The path is documented so nothing has to be inferred. -
The truth is in code.
tokens.cssis the runtime. Specs point at real component files at real paths. Nothing drifts behind a screenshot.
Where to go next
This article describes the destination. Article 3 covers the audit: how to map what already exists before scaffolding any of this, starting from the actual codebase you have rather than the ideal one you'd like.
Article 4 covers the wiring: how tokens.css connects to Tailwind v4's @theme layer and becomes live in the browser. Article 5 goes deep on writing specs that are actually useful to AI, as opposed to specs that are just documentation nobody reads.
