design.md Reference
Complete specification of the design.md format. Every section, field, accepted value, and constraint documented with examples — the exact file an AI needs to generate a complete TOUI theme.
What this file is
design.md is a structured natural-language document placed at the root of your project. It describes your brand so precisely that an AI can regenerate every token file from scratch without asking any follow-up questions.
The file has a fixed section order. AI tools pattern-match on the section headings, so do not rename them.
Full example: Meridian Design System
Below is a complete, production-quality design.md. Every section is annotated. Copy it, replace the values, and hand it to an AI.
# design.md — Meridian Design System
version: 1.0
updated: 2025-06-03
owner: Design Systems Team
---
## 1. Brand
### Identity
Name: Meridian
Tagline: Clarity in motion
Personality: trustworthy, precise, quietly confident
Voice: professional without being cold; direct without being terse
### Target audience
B2B SaaS — finance, logistics, operations teams
Users are power users who value density and information over decoration
### Design principles
1. Information density over whitespace — show more, scroll less
2. Precision over personality — exact, never approximate
3. Structural clarity — hierarchy is spatial, not decorative
4. Calm defaults — no unexpected motion, no surprising color
---
## 2. Colors
### Method
All colors are expressed in OKLCH for perceptual uniformity.
Format: oklch(lightness chroma hue)
lightness: 0 (black) → 1 (white)
chroma: 0 (grey) → ~0.4 (vivid)
hue: 0–360 degrees
### Palette (Tier 1 — tokens/base/palette.json)
Primary hue: 255 (indigo-blue)
Neutral hue: 255 (slight blue tint on greys)
| Name | Light value | Dark value |
|----------------|------------------------------|------------------------------|
| brand-50 | oklch(0.97 0.015 255) | — |
| brand-100 | oklch(0.93 0.03 255) | — |
| brand-200 | oklch(0.86 0.06 255) | — |
| brand-300 | oklch(0.76 0.10 255) | — |
| brand-400 | oklch(0.65 0.16 255) | — |
| brand-500 | oklch(0.55 0.22 255) | — |
| brand-600 | oklch(0.46 0.22 255) | — |
| brand-700 | oklch(0.37 0.18 255) | — |
| brand-800 | oklch(0.28 0.13 255) | — |
| brand-900 | oklch(0.18 0.08 255) | — |
| neutral-50 | oklch(0.985 0.005 255) | — |
| neutral-100 | oklch(0.96 0.008 255) | — |
| neutral-200 | oklch(0.92 0.008 255) | — |
| neutral-300 | oklch(0.85 0.008 255) | — |
| neutral-400 | oklch(0.72 0.008 255) | — |
| neutral-500 | oklch(0.58 0.008 255) | — |
| neutral-600 | oklch(0.45 0.008 255) | — |
| neutral-700 | oklch(0.32 0.008 255) | — |
| neutral-800 | oklch(0.21 0.008 255) | — |
| neutral-900 | oklch(0.13 0.008 255) | — |
### Semantic colors (Tier 2 — tokens/semantic/colors.json)
For each token: first value is light mode, value after → is dark mode.
**Surface**
- background: oklch(0.985 0.005 255) → oklch(0.12 0.008 255)
- foreground: oklch(0.13 0.008 255) → oklch(0.97 0.005 255)
- card: oklch(1 0 0) → oklch(0.18 0.008 255)
- card-foreground: same as foreground
- popover: same as card
- popover-foreground: same as foreground
- sidebar: oklch(0.96 0.008 255) → oklch(0.15 0.008 255)
- sidebar-foreground: same as foreground
**Brand**
- primary: oklch(0.46 0.22 255) → oklch(0.65 0.16 255)
- primary-foreground: oklch(0.985 0 0) → oklch(0.985 0 0)
- secondary: oklch(0.93 0.03 255) → oklch(0.22 0.01 255)
- secondary-foreground: oklch(0.13 0.008 255) → oklch(0.97 0.005 255)
- muted: oklch(0.96 0.008 255) → oklch(0.19 0.008 255)
- muted-foreground: oklch(0.45 0.008 255) → oklch(0.65 0.008 255)
- accent: oklch(0.93 0.03 255) → oklch(0.22 0.01 255)
- accent-foreground: same as foreground
**Feedback**
- destructive: oklch(0.577 0.245 27) → oklch(0.396 0.141 26)
- destructive-foreground: oklch(0.985 0 0) → oklch(0.985 0 0)
- info: oklch(0.55 0.22 255) → oklch(0.65 0.16 255)
- info-foreground: oklch(0.985 0 0) → oklch(0.985 0 0)
- success: oklch(0.58 0.18 149) → oklch(0.44 0.12 151)
- success-foreground: oklch(0.985 0 0) → oklch(0.985 0 0)
- warning: oklch(0.75 0.18 70) → oklch(0.65 0.17 58)
- warning-foreground: oklch(0.15 0 0) → oklch(0.985 0 0)
**Chrome**
- border: oklch(0.88 0.008 255) → oklch(1 0 0 / 10%)
- input: oklch(0.88 0.008 255) → oklch(1 0 0 / 15%)
- ring: oklch(0.55 0.22 255) → oklch(0.65 0.16 255)
**Sidebar brand**
- sidebar-primary: same as primary
- sidebar-primary-foreground: same as primary-foreground
- sidebar-accent: same as accent
- sidebar-accent-foreground: same as accent-foreground
- sidebar-border: same as border
- sidebar-ring: same as ring
**Charts** (keep sensible defaults, just adjust hue)
- chart-1: oklch(0.55 0.22 255) → oklch(0.65 0.16 255)
- chart-2: oklch(0.58 0.18 149) → oklch(0.70 0.17 162)
- chart-3: oklch(0.38 0.07 227) → oklch(0.77 0.19 70)
- chart-4: oklch(0.83 0.19 84) → oklch(0.63 0.27 304)
- chart-5: oklch(0.77 0.19 70) → oklch(0.65 0.25 16)
---
## 3. Shape
### Border radius
Base radius: 0.375rem (6px — tighter than shadcn default 10px, more precise)
The TOUI radius scale is derived from this base:
- --radius-xs: 0.125rem (hardcoded)
- --radius-sm: calc(base - 4px)
- --radius-md: calc(base - 2px)
- --radius-lg: base
- --radius-xl: calc(base + 4px)
- --radius-2xl: calc(base + 8px)
Rationale: Meridian targets data-heavy interfaces. Tighter radii signal
precision and make dense tables / grids feel structured rather than playful.
### Tokens to write
File: tokens/semantic/radii.json
Set: "radius.$value" = "0.375rem"
---
## 4. Spacing & component scale
### Philosophy
Dense. The default shadcn scale (md = h-9 / 36px) is appropriate.
Do not inflate heights — users are on desktop, information density matters.
### Scale (tokens/semantic/scale.json)
Keep all component.height.* as the shadcn defaults (inherit from --component-height-*).
No overrides needed — the base scale is correct for Meridian.
### Padding
Reduce default horizontal padding by one step to match the denser feel:
- component.px.md: var(--component-px-sm) ← was var(--component-px-md)
- component.px.lg: var(--component-px-md) ← was var(--component-px-xl)
### Gaps
Keep defaults.
---
## 5. Typography
### Font stack
- Sans: 'Inter Variable', system-ui, sans-serif (already loaded via @fontsource-variable/inter)
- Mono: 'Geist Mono', 'Fira Code', monospace
- Serif: optional — not used in primary UI
### Scale
Keep TOUI defaults (text-sm for most components, text-base for body).
Do not override --component-text-* tokens.
---
## 6. Component overrides
List each component that needs values different from the semantic scale.
### Button
- radius: var(--radius-sm) ← tighter than default --component-radius-sm
Rationale: buttons should feel precise, not soft
### Input
- radius: var(--radius-sm)
Rationale: match buttons for visual consistency
### Card
- radius: var(--radius-md)
Rationale: cards can be slightly more open than interactive elements
### Badge
- radius: var(--radius-xs)
Rationale: badges in data tables should be chip-like, not pill-like
### Avatar
No overrides — keep defaults.
### Tabs
No overrides.
### Select / Combobox
- radius: var(--radius-sm) ← match input
### Dialog / Sheet / Popover
- radius: var(--radius-lg) ← slightly more open for overlays
### Toast
- radius: var(--radius-md)
### Sidebar
No sizing overrides — sidebar uses its own semantic vars.
---
## 7. Motion
Keep TOUI defaults. Meridian does not add custom animation tokens.
Prefer system motion-reduce where possible.
---
## 8. Themes
### Default theme
The semantic colors above ARE the default theme. No theme CSS override file needed for "default".
### Dark variant
Handled by next-themes (.dark class). Dark values are already in semantic/colors.json
via $extensions.mode.dark. No separate theme file needed.
### Named themes
None for v1. The Meridian brand ships as a single visual identity.
Future: "high-contrast" accessibility theme may be added in v2.
---
## 9. Files for the AI to generate
When reading this file, generate EXACTLY these files and nothing else:
1. tokens/base/palette.json
— all brand-* and neutral-* colors from section 2 Palette
— no dark values (palette is light-mode only; semantic layer handles modes)
2. tokens/semantic/colors.json
— all semantic tokens from section 2 Semantic colors
— dark values go in $extensions.mode.dark
3. tokens/semantic/radii.json
— base radius: 0.375rem
— keep existing scale formula intact, only change the base value
4. tokens/semantic/scale.json
— only override component.px.md and component.px.lg as specified in section 4
— all other values keep their defaults
5. tokens/components/button.json
— override button.radius: var(--radius-sm)
6. tokens/components/input.json
— override input.radius: var(--radius-sm) (if the token exists, else skip)
7. tokens/components/badge.json
— override badge.radius: var(--radius-xs)
8. tokens/components/dialog.json (if it exists)
— do NOT generate this file if the component doesn't have a token file yet
9. lib/themes.ts
— only the "default" theme entry (no additional named themes)
— primary swatch: oklch(0.46 0.22 255)
— primaryDark: oklch(0.65 0.16 255)
DO NOT generate:
- tokens/output/** (these are build artifacts — generated by npm run tokens:build)
- Any component source files (.tsx, .ts in registry/)
- styles/globals.css (imports don't change)
- Any file not listed above
---
## 10. Validation checklist
After generating, the AI should verify:
- [ ] All OKLCH lightness values are between 0.0 and 1.0
- [ ] All OKLCH chroma values are between 0.0 and 0.4
- [ ] All $value strings that reference other vars use exact var(--name) syntax
- [ ] All dark mode values are in $extensions.mode.dark, not separate files
- [ ] No hard-coded px values in Tier 2 or Tier 3 (use calc(var(--spacing) * N))
- [ ] badge.radius references var(--radius-xs), not a raw value
- [ ] button.radius references var(--radius-sm), not a raw value
- [ ] lib/themes.ts COLOR_THEMES array has the "default" entry with correct OKLCH swatchesSection reference
Section 1 — Brand
| Field | Type | Description |
|---|---|---|
Name | string | Brand/product name — used in generated comments |
Tagline | string | One-liner — context for AI tone decisions |
Personality | list | Adjectives the AI uses to choose between tight vs. open spacing |
Design principles | numbered list | Drives shape/density decisions — more important than colors |
Target audience | prose | Power users → dense; consumer → spacious |
Section 2 — Colors
Palette table columns:
| Column | Format | Notes |
|---|---|---|
| Name | brand-50 … brand-900 | Must be kebab-case, no spaces |
| Light value | oklch(L C H) | Palette has no dark values |
Semantic color table:
Every semantic token needs both a light value and a dark value (after →). The 35 required semantic token names are listed in the Theming docs. Missing tokens will cause the build to fall back to inherited CSS variable values — not an error, but a gap in intentional design.
Section 3 — Shape
The single most impactful token is --radius. The TOUI scale derives all 8 radius steps from this base using calc(). Set it once and every component updates.
Personality presets:
| Value | Character | Use case |
|---|---|---|
0.25rem | Sharp, technical | Developer tools, data platforms |
0.375rem | Precise, professional | B2B SaaS, finance |
0.625rem | Balanced (shadcn default) | General SaaS |
0.75rem | Friendly, approachable | Consumer apps, productivity |
1rem | Open, generous | Marketing sites, onboarding |
1.5rem | Playful, pill-like | Social apps, mobile-first |
Section 4 — Spacing & component scale
Component heights drive the perceived density of the UI. The TOUI defaults match shadcn (md = h-9 / 36px). Override only when you have a specific reason.
Common density adjustments:
Dense (data platforms):
component.height.md: calc(var(--spacing) * 8) ← 32px
component.px.md: calc(var(--spacing) * 3) ← 12px
Default (shadcn):
component.height.md: calc(var(--spacing) * 9) ← 36px
component.px.md: calc(var(--spacing) * 4) ← 16px
Comfortable (consumer):
component.height.md: calc(var(--spacing) * 10) ← 40px
component.px.md: calc(var(--spacing) * 5) ← 20px
Section 5 — Typography
Fonts are set via tokens/base/brand.json. The AI should update font.sans.$value and font.mono.$value only if you specify a different font. Do not change text-size tokens unless your design explicitly requires a different scale.
Section 6 — Component overrides
List only components that deviate from the semantic scale. The AI maps these to tokens/components/{name}.json. Every token in those files points to a semantic var — never a hard-coded value.
Valid override targets per component:
| Component | Overrideable tokens |
|---|---|
button | radius, height.{xs–xl}, px.{xs–xl}, gap.{xs–xl} |
badge | radius, height.{sm–lg}, px.{sm–lg} |
input | radius, height.{sm–lg}, px.{sm–lg} |
card | radius, padding |
avatar | size.{xs–xl} |
dialog | radius |
toast | radius, padding |
select | radius, height.{sm–lg} |
Section 7 — Motion
Unless your brand has a specific motion personality, omit this section or write "keep defaults". The AI will not touch tokens/base/motion.json.
Section 8 — Themes
If your brand has multiple named themes (e.g. seasonal, white-label, high-contrast), list each one with:
- Theme name (used as
data-themeattribute value) - Which semantic tokens it overrides (color, radius, sizing)
- Light and dark values for each override
Section 9 — Files for the AI to generate
This is the most important section. An explicit file list prevents the AI from:
- Overwriting build artifacts (
tokens/output/) - Modifying component source files
- Generating files it wasn't asked for
Always include it. Always write DO NOT generate: with the artifacts list.
Section 10 — Validation checklist
The AI runs this checklist against its own output before returning it. Any failure gets fixed silently. Include it verbatim.
Minimal design.md (starter)
If you don't have a complete brand spec yet, use this skeleton. The AI will make sensible defaults for anything omitted.
# design.md
version: 0.1
## 1. Brand
Name: [Your product name]
Personality: [3 adjectives]
Target audience: [One sentence]
## 2. Colors
Primary hue: [0–360, e.g. 255 for blue, 150 for green, 25 for orange]
Primary lightness (light mode): [0.4–0.6 for vivid colors]
Neutral base: [same hue as primary, or 0 for pure grey]
Background preference: [pure white / warm white / tinted]
## 3. Shape
Base radius: [0.25rem / 0.375rem / 0.625rem / 0.75rem / 1rem / 1.5rem]
Density: [dense / default / comfortable]
## 4. Component overrides
[list any components that should deviate from the global shape/density]
## 9. Files for the AI to generate
1. tokens/semantic/colors.json
2. tokens/semantic/radii.json
3. tokens/output/themes/[your-theme-name].css
4. lib/themes.ts
DO NOT generate: tokens/output/**, registry/**AI prompt template
Copy this prompt and paste it with your design.md content:
You are a design token engineer. You will read the design.md specification below
and generate all the token files it requests.
Rules:
- Follow the TOUI 3-tier token architecture (base → semantic → component)
- All colors MUST be in oklch(L C H) format
• L: 0.0–1.0 (0=black, 1=white)
• C: 0.0–0.40 (0=grey, 0.4=vivid)
• H: 0–360
- Dark mode values go in $extensions.mode.dark — never in separate files
- Sizing values MUST use calc(var(--spacing) * N) — never hard-coded px
- Tier 3 component tokens MUST reference semantic vars (var(--component-*)) — never hard-coded values
- Generate ONLY the files listed in section 9 of design.md
- Do NOT generate tokens/output/** files — those are build artifacts
- After generating, run the validation checklist in section 10 and fix any failures silently
- Return each file as a separate code block with the file path as the title
<design.md>
[PASTE YOUR DESIGN.MD CONTENT HERE]
</design.md>
TOUI token schema reference (abbreviated):
tokens/base/palette.json structure:
{
"color-name": { "$type": "color", "$value": "oklch(...)" }
}
tokens/semantic/colors.json structure:
{
"token-name": {
"$type": "color",
"$value": "oklch(...)", // light mode
"$extensions": { "mode": { "dark": "oklch(...)" } }
}
}
tokens/semantic/radii.json structure:
{
"radius": { "$type": "dimension", "$value": "0.625rem" },
"radius-sm": { "$type": "dimension", "$value": "calc(var(--radius) - 4px)" },
"radius-md": { "$type": "dimension", "$value": "calc(var(--radius) - 2px)" },
"radius-lg": { "$type": "dimension", "$value": "var(--radius)" },
"radius-xl": { "$type": "dimension", "$value": "calc(var(--radius) + 4px)" }
}
tokens/semantic/scale.json structure (excerpt):
{
"component": {
"height": {
"md": { "$type": "dimension", "$value": "calc(var(--spacing) * 9)" }
},
"px": {
"md": { "$type": "dimension", "$value": "calc(var(--spacing) * 4)" }
}
}
}
tokens/components/button.json structure:
{
"button": {
"radius": { "$type": "dimension", "$value": "var(--component-radius-sm)" },
"height": {
"md": { "$type": "dimension", "$value": "var(--component-height-md)" }
}
}
}
tokens/output/themes/name.css structure:
[data-theme='name'] {
--primary: oklch(...);
--ring: oklch(...);
--radius: ...rem;
}
[data-theme='name'].dark {
--primary: oklch(...);
--ring: oklch(...);
}
lib/themes.ts COLOR_THEMES entry structure:
{
name: 'theme-name',
label: 'Display Name',
description: 'Adjective · adjective',
primary: 'oklch(...)', // light mode swatch
primaryDark: 'oklch(...)', // dark mode swatch
}