Design System Skill
Patterns for analyzing design tokens and mapping between mocks and NextSpark themes.
Fundamental Principle
THE DESIGN SYSTEM IS THEME-DEPENDENT.
All values in this skill are EXAMPLES from the default theme.
You MUST read the actual theme's globals.css to get real values.
bash1# Determine active theme 2grep "NEXT_PUBLIC_ACTIVE_THEME" .env .env.local 3 4# Read theme tokens 5cat contents/themes/{THEME}/styles/globals.css
Theme Token Locations
contents/themes/{THEME}/
├── styles/
│ ├── globals.css # CSS variables (:root and .dark)
│ └── components.css # Component-specific styles
├── config/
│ └── theme.config.ts # Theme metadata
CSS Variable Structure
Light Mode (:root)
css1:root { 2 /* Surface Colors */ 3 --background: oklch(1 0 0); /* Page background */ 4 --foreground: oklch(0.145 0 0); /* Primary text */ 5 --card: oklch(1 0 0); /* Card surfaces */ 6 --card-foreground: oklch(0.145 0 0); /* Card text */ 7 --popover: oklch(1 0 0); /* Dropdowns */ 8 --popover-foreground: oklch(0.145 0 0); 9 10 /* Interactive Colors */ 11 --primary: oklch(0.205 0 0); /* Primary actions */ 12 --primary-foreground: oklch(0.985 0 0); 13 --secondary: oklch(0.97 0 0); /* Secondary actions */ 14 --secondary-foreground: oklch(0.205 0 0); 15 --accent: oklch(0.97 0 0); /* Highlights */ 16 --accent-foreground: oklch(0.205 0 0); 17 18 /* State Colors */ 19 --muted: oklch(0.97 0 0); /* Muted backgrounds */ 20 --muted-foreground: oklch(0.556 0 0); /* Placeholder text */ 21 --destructive: oklch(0.577 0.245 27); /* Error/danger */ 22 --destructive-foreground: oklch(1 0 0); 23 24 /* Border & Input */ 25 --border: oklch(0.922 0 0); 26 --input: oklch(0.922 0 0); 27 --ring: oklch(0.708 0 0); /* Focus rings */ 28 29 /* Radius */ 30 --radius: 0.5rem; 31}
Dark Mode (.dark)
css1.dark { 2 --background: oklch(0.145 0 0); /* Inverted */ 3 --foreground: oklch(0.985 0 0); 4 --card: oklch(0.145 0 0); 5 --card-foreground: oklch(0.985 0 0); 6 7 --primary: oklch(0.922 0 0); /* Adjusted for dark */ 8 --primary-foreground: oklch(0.205 0 0); 9 10 --muted: oklch(0.269 0 0); 11 --muted-foreground: oklch(0.708 0 0); 12 13 --border: oklch(0.269 0 0); 14 --input: oklch(0.269 0 0); 15}
Color Format Conversion
Mocks often use HEX/RGB, themes use OKLCH.
HEX to OKLCH Mapping
| Mock (HEX) | Approximate OKLCH | Notes |
|---|---|---|
#ffffff | oklch(1 0 0) | Pure white |
#000000 | oklch(0 0 0) | Pure black |
#137fec | oklch(0.55 0.2 250) | Blue primary |
#101922 | oklch(0.15 0.02 260) | Dark background |
#00d4ff | oklch(0.75 0.15 200) | Cyan accent |
Similarity Calculation
Compare colors by:
- Lightness (L) - Most important, weight 0.5
- Chroma (C) - Saturation, weight 0.3
- Hue (H) - Color angle, weight 0.2
similarity = 1 - (
0.5 * |L1 - L2| +
0.3 * |C1 - C2| / maxChroma +
0.2 * |H1 - H2| / 360
)
Token Categories
Background Tokens
| Token | Tailwind Class | Usage |
|---|---|---|
--background | bg-background | Page background |
--card | bg-card | Card surfaces |
--popover | bg-popover | Dropdowns, menus |
--muted | bg-muted | Subtle backgrounds |
--accent | bg-accent | Hover states |
--primary | bg-primary | Primary buttons |
--secondary | bg-secondary | Secondary buttons |
--destructive | bg-destructive | Error states |
Foreground Tokens
| Token | Tailwind Class | Usage |
|---|---|---|
--foreground | text-foreground | Primary text |
--card-foreground | text-card-foreground | Card text |
--muted-foreground | text-muted-foreground | Secondary text |
--primary-foreground | text-primary-foreground | On primary bg |
--destructive-foreground | text-destructive-foreground | On error bg |
Border Tokens
| Token | Tailwind Class | Usage |
|---|---|---|
--border | border-border | Default borders |
--input | border-input | Input borders |
--ring | ring-ring | Focus rings |
Mapping Process
Step 1: Extract Mock Tokens
From Tailwind config or inline styles:
javascript1// From mock's tailwind.config 2const mockTokens = { 3 colors: { 4 primary: '#137fec', 5 'bg-dark': '#101922', 6 accent: '#00d4ff' 7 } 8}
Step 2: Read Theme Tokens
bash1# Extract all CSS variables 2grep -E "^\s*--" contents/themes/{theme}/styles/globals.css
Step 3: Create Mapping
For each mock token:
- Check exact match (hex → hex)
- Check semantic match (primary → --primary)
- Calculate color similarity
- Flag gaps if no good match
Step 4: Document Gaps
json1{ 2 "gaps": [ 3 { 4 "mockValue": "#ff5722", 5 "mockUsage": "accent icons", 6 "closestToken": "--destructive", 7 "similarity": 0.72, 8 "recommendation": "USE_CLOSEST or ADD_TOKEN" 9 } 10 ] 11}
Output Format: ds-mapping.json
json1{ 2 "theme": "default", 3 "themeGlobalsPath": "contents/themes/default/styles/globals.css", 4 "analyzedAt": "2025-01-09T12:00:00Z", 5 6 "themeTokens": { 7 "colors": { 8 "--background": "oklch(1 0 0)", 9 "--foreground": "oklch(0.145 0 0)", 10 "--primary": "oklch(0.205 0 0)", 11 "--secondary": "oklch(0.97 0 0)", 12 "--accent": "oklch(0.97 0 0)", 13 "--muted": "oklch(0.97 0 0)", 14 "--destructive": "oklch(0.577 0.245 27.325)" 15 }, 16 "radius": "0.5rem", 17 "fonts": { 18 "sans": "var(--font-sans)", 19 "mono": "var(--font-mono)" 20 } 21 }, 22 23 "mockTokens": { 24 "colors": { 25 "primary": "#137fec", 26 "background-dark": "#101922", 27 "accent": "#00d4ff", 28 "text-light": "#ffffff", 29 "text-muted": "#94a3b8" 30 } 31 }, 32 33 "colorMapping": [ 34 { 35 "id": "color-1", 36 "mockValue": "#137fec", 37 "mockName": "primary", 38 "mockUsage": ["buttons", "links", "focus rings"], 39 "themeToken": "--primary", 40 "themeValue": "oklch(0.205 0 0)", 41 "tailwindClass": "bg-primary text-primary-foreground", 42 "matchType": "semantic", 43 "similarity": 0.65, 44 "notes": "Theme primary is darker, mock is more vibrant blue" 45 }, 46 { 47 "id": "color-2", 48 "mockValue": "#101922", 49 "mockName": "background-dark", 50 "mockUsage": ["hero background", "footer"], 51 "themeToken": "--background", 52 "themeValue": "oklch(0.145 0 0)", 53 "tailwindClass": "bg-background", 54 "matchType": "closest", 55 "similarity": 0.88, 56 "notes": "Use dark mode or bg-gray-900" 57 } 58 ], 59 60 "typographyMapping": [ 61 { 62 "mockFont": "Inter", 63 "themeToken": "--font-sans", 64 "tailwindClass": "font-sans", 65 "matchType": "exact" 66 } 67 ], 68 69 "spacingMapping": [ 70 { 71 "mockValue": "24px", 72 "tailwindClass": "p-6", 73 "matchType": "exact" 74 } 75 ], 76 77 "radiusMapping": [ 78 { 79 "mockValue": "8px", 80 "themeToken": "--radius", 81 "themeValue": "0.5rem", 82 "tailwindClass": "rounded-lg", 83 "matchType": "exact" 84 } 85 ], 86 87 "gaps": [ 88 { 89 "type": "color", 90 "mockValue": "#00d4ff", 91 "mockName": "accent", 92 "mockUsage": ["terminal prompt", "code highlights"], 93 "closestToken": "--primary", 94 "similarity": 0.45, 95 "recommendations": [ 96 { 97 "option": "A", 98 "action": "Use --primary", 99 "impact": "Loses cyan accent, uses theme primary" 100 }, 101 { 102 "option": "B", 103 "action": "Add --accent-cyan to theme", 104 "impact": "Requires theme modification" 105 }, 106 { 107 "option": "C", 108 "action": "Use inline text-[#00d4ff]", 109 "impact": "Not recommended, breaks theming" 110 } 111 ] 112 } 113 ], 114 115 "summary": { 116 "totalMockTokens": 12, 117 "mapped": 10, 118 "gaps": 2, 119 "overallCompatibility": 0.83, 120 "recommendation": "PROCEED_WITH_GAPS" 121 } 122}
Output Format: block-plan.json (BLOCKS workflow only)
For the BLOCKS workflow, an additional output is generated to guide block development:
json1{ 2 "mockPath": "mocks/", 3 "analyzedAt": "2026-01-12T12:00:00Z", 4 "workflow": "BLOCKS", 5 6 "existingBlocks": [ 7 { 8 "name": "hero-simple", 9 "similarity": 0.85, 10 "matchReason": "Similar layout and components" 11 }, 12 { 13 "name": "hero-centered", 14 "similarity": 0.72, 15 "matchReason": "Centered text, different background" 16 } 17 ], 18 19 "decision": { 20 "type": "new" | "variant" | "existing", 21 "blockName": "hero-terminal", 22 "baseBlock": "hero-simple", 23 "reasoning": "Requires custom terminal animation component not in existing blocks" 24 }, 25 26 "blockSpec": { 27 "name": "hero-terminal", 28 "category": "hero", 29 "fields": [ 30 {"name": "title", "type": "text", "required": true}, 31 {"name": "subtitle", "type": "text", "required": false}, 32 {"name": "primaryCta", "type": "link", "required": true}, 33 {"name": "secondaryCta", "type": "link", "required": false}, 34 {"name": "terminalContent", "type": "textarea", "required": true} 35 ], 36 "customComponents": ["TerminalAnimation"], 37 "estimatedComplexity": "medium" 38 }, 39 40 "developmentNotes": [ 41 "Terminal animation requires custom React component", 42 "Use existing Button component for CTAs", 43 "Background gradient matches theme --background token" 44 ] 45}
Decision Types
| Type | When to Use | Action |
|---|---|---|
existing | Mock matches existing block 90%+ | Use existing block, no changes |
variant | Mock matches but needs minor additions | Extend existing block with new variant |
new | Mock requires significant new functionality | Create new block from scratch |
Workflow Integration
| Workflow | ds-mapping.json | block-plan.json | When Generated |
|---|---|---|---|
| BLOCKS | Yes | Yes | Phase 1 (Mock Analysis) |
| TASK | Yes (if mock) | No | Phase 0.6 (if mock selected) |
| STORY | Yes (if mock) | No | Phase 0.6 (if mock selected) |
| TWEAK | No | No | N/A |
Reusability
This skill applies to ANY design-to-code conversion:
- Landing pages (mocks → blocks)
- Email templates (design → HTML)
- PDF templates (design → React-PDF)
- Marketing materials
Generating globals.css from Mock (One-Time Setup)
Use this section when initializing a theme from a design mock. This is typically a one-time setup task during theme creation.
When to Use
- New theme creation from design mock
- Theme initialization via
how-to:customize-themecommand - Converting a purchased template to NextSpark theme
Step 1: Convert HEX to OKLCH
For each color in mock's Tailwind config, convert from HEX to OKLCH:
javascript1function hexToOklch(hex) { 2 // Remove # if present 3 hex = hex.replace('#', '') 4 5 // Parse RGB 6 const r = parseInt(hex.substr(0, 2), 16) / 255 7 const g = parseInt(hex.substr(2, 2), 16) / 255 8 const b = parseInt(hex.substr(4, 2), 16) / 255 9 10 // Convert to linear RGB 11 const rL = r <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4) 12 const gL = g <= 0.04045 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4) 13 const bL = b <= 0.04045 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4) 14 15 // Approximate OKLCH 16 const L = 0.4122 * rL + 0.5363 * gL + 0.0514 * bL 17 const lightness = Math.cbrt(L) 18 19 // Chroma and hue calculation 20 const max = Math.max(r, g, b) 21 const min = Math.min(r, g, b) 22 const chroma = (max - min) * 0.3 23 24 let hue = 0 25 if (max !== min) { 26 if (max === r) hue = 60 * (((g - b) / (max - min)) % 6) 27 else if (max === g) hue = 60 * ((b - r) / (max - min) + 2) 28 else hue = 60 * ((r - g) / (max - min) + 4) 29 if (hue < 0) hue += 360 30 } 31 32 return `oklch(${lightness.toFixed(4)} ${chroma.toFixed(4)} ${hue.toFixed(0)})` 33}
Step 2: Generate Dark Mode (Invert Lightness)
For dark mode, invert the lightness (L) value:
javascript1function invertLightnessOklch(oklchValue) { 2 // Parse oklch(L C H) 3 const match = oklchValue.match(/oklch\(([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\)/) 4 if (!match) return oklchValue 5 6 const L = parseFloat(match[1]) 7 const C = parseFloat(match[2]) 8 const H = parseFloat(match[3]) 9 10 // Invert lightness: L' = 1 - L 11 const invertedL = 1 - L 12 13 return `oklch(${invertedL.toFixed(4)} ${C.toFixed(4)} ${H.toFixed(0)})` 14}
Which tokens to invert:
--background↔--foreground(swap)--card↔--card-foreground(swap)--popover↔--popover-foreground(swap)--muted→ invert--muted-foreground→ invert--border,--input→ invert--primary,--secondary,--accent→ typically keep similar or slightly adjust
Step 3: globals.css Template
css1/** 2 * Theme: {theme} 3 * Generated from mock: {mockPath} 4 * Date: {timestamp} 5 * 6 * NOTE: Dark mode was auto-generated by inverting lightness values. 7 * Review and adjust .dark {} section as needed for your brand. 8 */ 9 10:root { 11 /* Surface Colors */ 12 --background: {oklch from mock}; 13 --foreground: {oklch from mock}; 14 --card: {oklch from mock or default}; 15 --card-foreground: {oklch from mock or default}; 16 --popover: {oklch from mock or default}; 17 --popover-foreground: {oklch from mock or default}; 18 19 /* Interactive Colors */ 20 --primary: {oklch from mock}; 21 --primary-foreground: {calculated contrast}; 22 --secondary: {oklch from mock or default}; 23 --secondary-foreground: {calculated contrast}; 24 --accent: {oklch from mock or default}; 25 --accent-foreground: {calculated contrast}; 26 27 /* State Colors */ 28 --muted: {oklch from mock or default}; 29 --muted-foreground: {oklch from mock or default}; 30 --destructive: oklch(0.577 0.245 27.325); 31 --destructive-foreground: oklch(1 0 0); 32 33 /* Border & Input */ 34 --border: {oklch from mock or default}; 35 --input: {oklch from mock or default}; 36 --ring: {oklch from mock or default}; 37 38 /* Chart Colors */ 39 --chart-1: oklch(0.81 0.1 252); 40 --chart-2: oklch(0.62 0.19 260); 41 --chart-3: oklch(0.55 0.22 263); 42 --chart-4: oklch(0.49 0.22 264); 43 --chart-5: oklch(0.42 0.18 266); 44 45 /* Sidebar */ 46 --sidebar: {based on background}; 47 --sidebar-foreground: {based on foreground}; 48 --sidebar-primary: {based on primary}; 49 --sidebar-primary-foreground: {based on primary-foreground}; 50 --sidebar-accent: {based on accent}; 51 --sidebar-accent-foreground: {based on accent-foreground}; 52 --sidebar-border: {based on border}; 53 --sidebar-ring: {based on ring}; 54 55 /* Typography */ 56 --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; 57 --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; 58 --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; 59 60 /* Design Tokens */ 61 --radius: 0.625rem; 62 --spacing: 0.25rem; 63 64 /* Shadows */ 65 --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); 66 --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); 67 --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); 68 --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); 69 --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10); 70 --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10); 71 --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10); 72 --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); 73} 74 75/* ============================================= 76 DARK MODE (Auto-generated by inverting lightness) 77 Review and adjust as needed for your brand 78 ============================================= */ 79 80.dark { 81 --background: {inverted}; 82 --foreground: {inverted}; 83 --card: {inverted}; 84 --card-foreground: {inverted}; 85 --popover: {inverted}; 86 --popover-foreground: {inverted}; 87 --primary: {adjusted for dark}; 88 --primary-foreground: {adjusted for dark}; 89 --secondary: {inverted}; 90 --secondary-foreground: {inverted}; 91 --muted: {inverted}; 92 --muted-foreground: {inverted}; 93 --accent: {inverted}; 94 --accent-foreground: {inverted}; 95 --destructive: oklch(0.704 0.191 22.216); 96 --destructive-foreground: oklch(0.985 0 0); 97 --border: {inverted}; 98 --input: {inverted}; 99 --ring: {inverted}; 100 --sidebar: {inverted}; 101 --sidebar-foreground: {inverted}; 102 --sidebar-primary: {adjusted}; 103 --sidebar-primary-foreground: {adjusted}; 104 --sidebar-accent: {inverted}; 105 --sidebar-accent-foreground: {inverted}; 106 --sidebar-border: {inverted}; 107 --sidebar-ring: {inverted}; 108} 109 110/* ============================================= 111 TAILWIND v4 THEME MAPPING 112 ============================================= */ 113 114@theme inline { 115 --color-background: var(--background); 116 --color-foreground: var(--foreground); 117 --color-card: var(--card); 118 --color-card-foreground: var(--card-foreground); 119 --color-popover: var(--popover); 120 --color-popover-foreground: var(--popover-foreground); 121 --color-primary: var(--primary); 122 --color-primary-foreground: var(--primary-foreground); 123 --color-secondary: var(--secondary); 124 --color-secondary-foreground: var(--secondary-foreground); 125 --color-muted: var(--muted); 126 --color-muted-foreground: var(--muted-foreground); 127 --color-accent: var(--accent); 128 --color-accent-foreground: var(--accent-foreground); 129 --color-destructive: var(--destructive); 130 --color-destructive-foreground: var(--destructive-foreground); 131 --color-border: var(--border); 132 --color-input: var(--input); 133 --color-ring: var(--ring); 134 --color-chart-1: var(--chart-1); 135 --color-chart-2: var(--chart-2); 136 --color-chart-3: var(--chart-3); 137 --color-chart-4: var(--chart-4); 138 --color-chart-5: var(--chart-5); 139 --color-sidebar: var(--sidebar); 140 --color-sidebar-foreground: var(--sidebar-foreground); 141 --color-sidebar-primary: var(--sidebar-primary); 142 --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 143 --color-sidebar-accent: var(--sidebar-accent); 144 --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 145 --color-sidebar-border: var(--sidebar-border); 146 --color-sidebar-ring: var(--sidebar-ring); 147 148 --font-sans: var(--font-sans); 149 --font-mono: var(--font-mono); 150 --font-serif: var(--font-serif); 151 152 --radius-sm: calc(var(--radius) - 4px); 153 --radius-md: calc(var(--radius) - 2px); 154 --radius-lg: var(--radius); 155 --radius-xl: calc(var(--radius) + 4px); 156 157 --shadow-2xs: var(--shadow-2xs); 158 --shadow-xs: var(--shadow-xs); 159 --shadow-sm: var(--shadow-sm); 160 --shadow: var(--shadow); 161 --shadow-md: var(--shadow-md); 162 --shadow-lg: var(--shadow-lg); 163 --shadow-xl: var(--shadow-xl); 164 --shadow-2xl: var(--shadow-2xl); 165}
Generation Checklist
- All mock colors converted to OKLCH
- Dark mode generated with inverted lightness
- Complete @theme inline section included
- Font stacks included
- Shadow definitions included
- Clear comment indicating dark mode was auto-generated
- Complete, valid CSS output
Related Skills
tailwind-theming- Detailed Tailwind CSS patternsshadcn-theming- shadcn/ui theme customizationmock-analysis- For extracting mock tokenspage-builder-blocks- For applying tokens to blocksblock-decision-matrix- For block new/variant/existing decisions