Design System Generator
Transform product artifacts into design systems that feel so natural users think "of course, how else would it be?"
Philosophy
Great design systems don't impose arbitrary aesthetics. They reveal what was always latent in the product's purpose. The goal is not to decorate but to crystallize the product's soul into visual and interaction patterns.
Before generating specifications, understand three things:
-
Who feels what? The target audience's emotional state before, during, and after using the product
-
What's the core tension? Every product resolves a tension. The design system should embody that resolution
-
What must feel inevitable? The final system should feel like the only possible answer
Token Architecture
This skill uses a three-layer token system inspired by shadcn/ui and the W3C Design Tokens specification. Every visual decision flows through three levels of abstraction:
PRIMITIVE (what exists) → color.blue.500, spacing.4, font.size.md ↓ SEMANTIC (what it means) → --primary, --muted-foreground, --border ↓ COMPONENT (where it goes) → button.primary.background, input.border.default
The background/foreground convention: Every semantic role comes as a pair. The base name (e.g., --primary ) is the background/fill. The -foreground suffix (e.g., --primary-foreground ) is the text/icon color on that surface. Always pair them: bg-primary text-primary-foreground .
Color format: Use OKLCH (Oklch Lightness Chroma Hue) as the primary color space. OKLCH provides perceptually uniform steps — equal numeric changes produce visually equal brightness changes, which is critical for accessible color scales. Provide hex fallbacks for documentation readability.
See references/token-architecture.md for the complete token system specification.
Input Requirements
Gather from the user:
-
Product description or PRD
-
Target audience profile (demographics, psychographics, context of use)
-
User flow or key screens/wireframes
-
Brand constraints if any (existing logos, colors, fonts)
-
Competitive context (optional but helpful)
If missing critical inputs, ask. Don't hallucinate audience or product details.
Output Structure
Generate a design system document with these sections:
- Emotional Foundation (Mandatory First Step)
Before any visual decisions, establish:
AUDIENCE EMOTIONAL MAPPING
Before product: [emotional state - frustration, confusion, desire, etc.] During product: [transformation happening] After product: [desired emotional outcome]
CORE TENSION: [what problem/desire the product resolves] DESIGN PROMISE: [one sentence: what the design must communicate]
Example:
AUDIENCE: Busy professionals tracking personal goals Before: Overwhelmed, scattered, guilty about unfulfilled intentions During: Clarity emerging, small wins accumulating After: Empowered, in control, proud of progress
CORE TENSION: Ambition vs. time scarcity DESIGN PROMISE: Progress should feel effortless and inevitable
- Primitive Tokens
Define the raw palette — all available values before any semantic meaning.
Neutral Base
Select a neutral base that matches the brand personality. Each base creates a distinct feel:
Base Undertone Feels Like
Neutral Pure gray Clean, stark, digital-native
Stone Warm gray Grounded, earthy, natural
Zinc Cool gray Industrial, precise, technical
Gray Blue-gray Corporate, reliable, familiar
Slate Blue-tinted Professional, established, calm
NEUTRAL BASE: [selected base]
Light mode scale: 50: [oklch value] / [hex] -- Backgrounds, subtle fills 100: [oklch value] / [hex] -- Alternate backgrounds 200: [oklch value] / [hex] -- Borders, dividers 300: [oklch value] / [hex] -- Disabled text, placeholders 400: [oklch value] / [hex] -- Muted text 500: [oklch value] / [hex] -- Secondary text 600: [oklch value] / [hex] -- Body text (light bg) 700: [oklch value] / [hex] -- Headings 800: [oklch value] / [hex] -- High emphasis 900: [oklch value] / [hex] -- Maximum emphasis 950: [oklch value] / [hex] -- Near-black
Dark mode scale: [reversed scale with adjusted lightness/chroma]
Brand Colors
BRAND COLOR PRIMITIVES
Primary scale (50-950): [full scale with oklch + hex, derived from the emotional foundation]
Secondary scale (50-950): [if needed — only when the brand demands a true second hue]
Accent scale (50-950): [for delight, celebration, highlights]
Destructive scale (50-950): [reds for error/danger states]
Typography Primitives
Select fonts that embody the emotional foundation. Reference references/typography-psychology.md .
FONT FAMILIES
Display: [Font name] - [why it fits the emotional promise] Body: [Font name] - [why it fits] Mono: [Font name] (if needed)
SIZE SCALE (base 16px): xs: 12px / 0.75rem sm: 14px / 0.875rem base: 16px / 1rem lg: 18px / 1.125rem xl: 20px / 1.25rem 2xl: 24px / 1.5rem 3xl: 30px / 1.875rem 4xl: 36px / 2.25rem 5xl: 48px / 3rem
WEIGHT SCALE: regular: 400 -- Body text medium: 500 -- Labels, emphasis semibold: 600 -- Headlines, buttons bold: 700 -- Hero text
LINE HEIGHT: tight: 1.25 -- Headlines, display normal: 1.5 -- Body text relaxed: 1.75 -- Long-form reading
LETTER SPACING: tight: -0.025em -- Display, large text normal: 0 -- Body wide: 0.025em -- Labels, caps, small text
Spacing & Radius Primitives
SPACING SCALE (base 4px): 0: 0 1: 4px / 0.25rem 2: 8px / 0.5rem 3: 12px / 0.75rem 4: 16px / 1rem 5: 20px / 1.25rem 6: 24px / 1.5rem 8: 32px / 2rem 10: 40px / 2.5rem 12: 48px / 3rem 16: 64px / 4rem 20: 80px / 5rem 24: 96px / 6rem
RADIUS SCALE: none: 0 sm: 0.25rem / 4px md: 0.375rem / 6px lg: 0.5rem / 8px xl: 0.75rem / 12px 2xl: 1rem / 16px full: 9999px
- Semantic Tokens
Map primitives to UI roles. This is the design system's API — the contract between design decisions and implementation. Every value references a primitive, never a raw value.
The Complete Semantic Token Map
SEMANTIC TOKENS (light / dark)
--- Core Surfaces --- --background: [page background] --foreground: [default text on page background]
--card: [card/elevated surface] --card-foreground: [text on card]
--popover: [dropdown/tooltip/overlay surface] --popover-foreground: [text on popover]
--- Interactive --- --primary: [main brand action — buttons, links] --primary-foreground: [text/icon on primary]
--secondary: [secondary actions, subtle buttons] --secondary-foreground: [text on secondary]
--accent: [hover highlights, subtle emphasis] --accent-foreground: [text on accent]
--destructive: [danger/error actions] --destructive-foreground:[text on destructive]
--muted: [disabled backgrounds, subtle fills] --muted-foreground: [placeholder text, disabled text, captions]
--- Structural --- --border: [default border color] --input: [input field border color] --ring: [focus ring color]
--- Global Shape --- --radius: [global border radius — brand personality]
--- Data Visualization --- --chart-1: [primary chart color] --chart-2: [secondary chart color] --chart-3: [tertiary chart color] --chart-4: [quaternary chart color] --chart-5: [quinary chart color]
--- Sidebar (complex apps) --- --sidebar: [sidebar background] --sidebar-foreground: [sidebar text] --sidebar-primary: [sidebar active/selected item] --sidebar-primary-foreground: [text on sidebar active item] --sidebar-accent: [sidebar hover state] --sidebar-accent-foreground: [text on sidebar hover] --sidebar-border: [sidebar border/divider] --sidebar-ring: [sidebar focus ring]
Radius as brand personality: The --radius token is a single value that defines the entire brand feel:
Radius Personality
0 Sharp, serious, enterprise
0.25rem Professional, restrained
0.375rem Balanced, modern default
0.5rem Friendly, approachable
0.75rem Soft, consumer-friendly
1rem+ Playful, app-like
Mapping Example
Show explicitly how primitives map to semantic tokens:
MAPPING: Primitives → Semantic
Given: primary = blue, neutral base = slate
Light Mode: --background: slate.50 oklch(0.984 0.003 247) / #f8fafc --foreground: slate.950 oklch(0.129 0.042 264) / #020617 --primary: blue.600 oklch(0.546 0.245 262) / #2563eb --primary-foreground: white oklch(1 0 0) / #ffffff --muted: slate.100 oklch(0.968 0.007 247) / #f1f5f9 --muted-foreground: slate.500 oklch(0.554 0.046 257) / #64748b --border: slate.200 oklch(0.929 0.013 255) / #e2e8f0 --input: slate.200 oklch(0.929 0.013 255) / #e2e8f0 --ring: blue.600 oklch(0.546 0.245 262) / #2563eb --radius: 0.625rem
Dark Mode: --background: slate.950 oklch(0.129 0.042 264) / #020617 --foreground: slate.50 oklch(0.984 0.003 247) / #f8fafc --primary: blue.500 oklch(0.623 0.214 259) / #3b82f6 --primary-foreground: slate.950 oklch(0.129 0.042 264) / #020617 --muted: slate.800 oklch(0.279 0.041 260) / #1e293b --muted-foreground: slate.400 oklch(0.704 0.04 256) / #94a3b8 --border: slate.800 oklch(0.279 0.041 260) / #1e293b --input: slate.800 oklch(0.279 0.041 260) / #1e293b --ring: blue.500 oklch(0.623 0.214 259) / #3b82f6 --radius: 0.625rem
- Component Tokens
Map semantic tokens to specific components. This layer is optional for simple systems but valuable for complex or multi-brand products.
COMPONENT TOKENS
button.primary.background: var(--primary) button.primary.foreground: var(--primary-foreground) button.primary.hover: [primary with adjusted lightness] button.secondary.background: var(--secondary) button.secondary.foreground: var(--secondary-foreground) button.destructive.background: var(--destructive) button.destructive.foreground: var(--destructive-foreground) button.ghost.background: transparent button.ghost.foreground: var(--foreground) button.ghost.hover: var(--accent)
input.background: var(--background) input.border: var(--input) input.border.focus: var(--ring) input.placeholder: var(--muted-foreground)
card.background: var(--card) card.foreground: var(--card-foreground) card.border: var(--border)
badge.default.background: var(--primary) badge.default.foreground: var(--primary-foreground) badge.secondary.background: var(--secondary) badge.secondary.foreground: var(--secondary-foreground) badge.destructive.background: var(--destructive) badge.destructive.foreground: var(--destructive-foreground) badge.outline.background: transparent badge.outline.border: var(--border)
- Component Patterns
Reference references/component-patterns.md for full patterns. Key specs here:
Buttons
BUTTON HIERARCHY
-
PRIMARY (one per view max) Tokens: bg-primary text-primary-foreground Hover: primary with +5% lightness Use: The ONE action you want users to take
-
SECONDARY Tokens: bg-secondary text-secondary-foreground Hover: secondary with +5% lightness Use: Alternative valid paths
-
DESTRUCTIVE Tokens: bg-destructive text-destructive-foreground Use: Delete, remove, cancel subscription
-
OUTLINE Tokens: border-input bg-background text-foreground Hover: bg-accent text-accent-foreground Use: Lower-priority actions
-
GHOST Tokens: bg-transparent text-foreground Hover: bg-accent text-accent-foreground Use: Toolbar actions, inline actions
-
LINK Tokens: text-primary underline Use: Inline text actions
BUTTON SIZES: sm: h-8 px-3 text-xs rounded-[calc(var(--radius)-2px)] md: h-9 px-4 text-sm rounded-[var(--radius)] lg: h-10 px-6 text-sm rounded-[var(--radius)] xl: h-11 px-8 text-base rounded-[var(--radius)] icon: h-9 w-9 rounded-[var(--radius)]
Form Elements
INPUT FIELDS
Height: h-9 (36px) to h-10 (40px) Padding: px-3 py-1 Border: 1px solid var(--input) Focus: ring-2 ring-[var(--ring)] ring-offset-2 Radius: rounded-[var(--radius)] Background: var(--background) Text: var(--foreground) Placeholder: var(--muted-foreground)
Label: text-sm font-medium text-foreground Helper: text-xs text-muted-foreground Error: text-xs text-destructive
VALIDATION:
- Inline on blur, not keystroke
- Error: border-destructive + error message below
- Success: subtle checkmark (optional)
Cards
CARD SPECIFICATION
Background: var(--card) Border: 1px solid var(--border) Radius: rounded-[var(--radius)] Shadow: sm (light mode), none (dark mode) Padding: p-6
Card Header: pb-2 Card Title: text-lg font-semibold text-card-foreground Card Description: text-sm text-muted-foreground Card Content: pt-0 Card Footer: pt-4 flex items-center
- Motion & Interaction
MOTION PRINCIPLES
Purpose: Guide attention, provide feedback, create delight Never: Delay users, distract from content, add without purpose
TIMING TOKENS: instant: 0ms -- State changes, toggles fast: 100-150ms -- Micro-interactions, hovers, focus rings normal: 200-300ms -- Transitions, reveals, collapses slow: 400-500ms -- Page transitions, celebrations
EASING: ease-out: entering elements (decelerate in) ease-in: exiting elements (accelerate out) ease-in-out: moving elements, layout shifts spring: playful interactions (if brand supports it)
INTERACTION FEEDBACK: Button press: scale(0.98) + slight darken, fast timing Hover: background token shift (e.g., accent), fast timing Focus: ring-2 ring-[var(--ring)] ring-offset-2, instant Success: brief pulse or checkmark, normal timing Error: subtle shake (2px, 3 cycles), fast timing Loading: skeleton shimmer or spinner, never frozen UI Accordion: height transition, normal timing, ease-out
- Accessibility Baseline
CONTRAST REQUIREMENTS (WCAG 2.1 AA): Normal text (<18px): 4.5:1 minimum Large text (18px+ bold): 3:1 minimum UI components: 3:1 minimum Focus indicators: 3:1 against adjacent colors
INTERACTIVE TARGETS: Minimum: 44x44px touch targets (WCAG 2.5.8) Spacing: 8px minimum between targets
FOCUS STATES: Visible focus ring on ALL interactive elements Style: ring-2 ring-[var(--ring)] ring-offset-2 ring-offset-[var(--background)] Never remove focus outline without replacement Keyboard navigation must work for all interactive patterns
COLOR INDEPENDENCE: Never use color alone to convey meaning Pair color with: icons, text labels, patterns, or position
MOTION SAFETY: Respect prefers-reduced-motion Provide reduced/no-animation fallbacks for all transitions
Process
-
Extract the emotional foundation from product docs
-
Select a neutral base that matches brand undertone
-
Define primitive scales (colors in OKLCH, type, spacing, radius)
-
Map primitives to semantic tokens using background/foreground pairs
-
Select typography that embodies the brand voice
-
Build color system around emotional moments
-
Specify component tokens referencing semantic layer
-
Add motion that feels natural, not decorative
-
Verify every token pair meets WCAG contrast requirements
-
Document dark mode values for every semantic token
Dark Mode Rules
Dark mode is not an afterthought — it's a parallel track built into the token system from step 1.
-
Every semantic token MUST have both light and dark values
-
Dark mode reduces saturation and shifts lightness, never inverts
-
Surface hierarchy uses multiple levels (background → card → popover), not just "black"
-
Primary colors shift lighter in dark mode for visibility
-
Foreground colors flip (dark text → light text) maintaining contrast ratios
-
Semantic colors (success, error, warning) preserve hue but adjust lightness
-
Borders become more subtle (lower contrast against dark surfaces)
-
Shadows are usually removed or replaced with subtle border differentiation
References
-
references/token-architecture.md
-
Three-layer token system, complete semantic token map, neutral presets
-
references/typography-psychology.md
-
Font personality mappings and pairing rules
-
references/color-emotion.md
-
Color psychology, OKLCH values, audience palettes
-
references/component-patterns.md
-
Common UI patterns with token consumption maps
Anti-Patterns
-
Token soup: Too many options create decision paralysis. ~20 semantic tokens cover an entire component library. Constrain choices.
-
Aesthetic without purpose: Every decision should trace back to user emotion and the design promise
-
Skipping the semantic layer: Never reference primitives directly in components. Always go through semantic tokens.
-
Mismatched pairs: Using bg-primary text-primary instead of bg-primary text-primary-foreground . The foreground must always be the readable counterpart.
-
Hex-only thinking: Use OKLCH for perceptually uniform color manipulation. Hex is for documentation readability.
-
Dark mode as inversion: Flipping black/white is not dark mode. It requires deliberate surface hierarchy and adjusted chroma.
-
Ignoring context: A fitness app and a banking app need different energy even if targeting similar demographics
-
Trend-chasing: Glassmorphism and gradients aren't personality. What's the actual emotional intent?
-
Radius inconsistency: Use one global --radius value. All components derive from it, creating visual coherence.