Getting Started

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 swatches

Section reference

Section 1 — Brand

FieldTypeDescription
NamestringBrand/product name — used in generated comments
TaglinestringOne-liner — context for AI tone decisions
PersonalitylistAdjectives the AI uses to choose between tight vs. open spacing
Design principlesnumbered listDrives shape/density decisions — more important than colors
Target audienceprosePower users → dense; consumer → spacious

Section 2 — Colors

Palette table columns:

ColumnFormatNotes
Namebrand-50brand-900Must be kebab-case, no spaces
Light valueoklch(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:

ValueCharacterUse case
0.25remSharp, technicalDeveloper tools, data platforms
0.375remPrecise, professionalB2B SaaS, finance
0.625remBalanced (shadcn default)General SaaS
0.75remFriendly, approachableConsumer apps, productivity
1remOpen, generousMarketing sites, onboarding
1.5remPlayful, pill-likeSocial 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:

ComponentOverrideable tokens
buttonradius, height.{xs–xl}, px.{xs–xl}, gap.{xs–xl}
badgeradius, height.{sm–lg}, px.{sm–lg}
inputradius, height.{sm–lg}, px.{sm–lg}
cardradius, padding
avatarsize.{xs–xl}
dialogradius
toastradius, padding
selectradius, 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-theme attribute 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
}

We use cookies

We use cookies to ensure you get the best experience on our website. For more information on how we use cookies, please see our cookie policy.

By clicking Accept, you agree to our use of cookies.
Learn more.