Design System Analysis

VerifiedSafe

Design token analysis and mapping between mocks and NextSpark themes. Covers token extraction, value mapping, and gap analysis.

Sby Skills Guide Bot
DevelopmentIntermediate
306/2/2026
Claude Code
#design-system#theme-tokens#css-variables#color-conversion#nextspark

Recommended for

Our review

Analyzes design tokens of a theme system, extracts CSS variables, maps mock values to themes, and identifies gaps.

Strengths

  • Automates color conversion between HEX/RGB and OKLCH formats.
  • Provides a weighted similarity calculation for color comparison.
  • Comprehensive list of tokens and their corresponding Tailwind classes.

Limitations

  • Requires the actual theme to be available in the project (globals.css).
  • Example values are not universal and must be read from the actual files.
  • Does not handle dynamic themes or computed CSS variables.
When to use it

Use this skill to align design mockups with existing tokens in a NextSpark project or to perform a theme audit.

When not to use it

Do not use it if the project lacks a structured theme system (no globals.css) or if working with inline styles.

Security analysis

Safe
Quality score88/100

The skill only reads theme-related CSS files and environment variables using safe commands (grep, cat). No destructive or exfiltration actions.

No concerns found

Examples

Extract theme tokens
Read the active theme's globals.css from contents/themes/default/styles/globals.css and list all CSS custom properties with their values.
Map mock colors to theme tokens
Given a mock design with colors #137fec, #ffffff, and #101922, find the closest matching theme tokens from globals.css using the similarity formula (weight: lightness 0.5, chroma 0.3, hue 0.2).
Gap analysis between mock and theme
Compare the mock design tokens (list: color1: #00d4ff for accent, color2: #f5f5f5 for background) against the theme's globals.css. Identify missing or mismatched tokens and suggest mappings.

name: design-system description: | Theme-aware design system analysis and token mapping. Covers extracting theme tokens, mapping mock values, and gap analysis. CRITICAL: All values are EXAMPLES - always read actual theme globals.css. allowed-tools: Read, Glob, Grep version: 1.0.0

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.

# Determine active theme
grep "NEXT_PUBLIC_ACTIVE_THEME" .env .env.local

# Read theme tokens
cat 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)

:root {
  /* Surface Colors */
  --background: oklch(1 0 0);           /* Page background */
  --foreground: oklch(0.145 0 0);       /* Primary text */
  --card: oklch(1 0 0);                 /* Card surfaces */
  --card-foreground: oklch(0.145 0 0);  /* Card text */
  --popover: oklch(1 0 0);              /* Dropdowns */
  --popover-foreground: oklch(0.145 0 0);

  /* Interactive Colors */
  --primary: oklch(0.205 0 0);          /* Primary actions */
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);         /* Secondary actions */
  --secondary-foreground: oklch(0.205 0 0);
  --accent: oklch(0.97 0 0);            /* Highlights */
  --accent-foreground: oklch(0.205 0 0);

  /* State Colors */
  --muted: oklch(0.97 0 0);             /* Muted backgrounds */
  --muted-foreground: oklch(0.556 0 0); /* Placeholder text */
  --destructive: oklch(0.577 0.245 27); /* Error/danger */
  --destructive-foreground: oklch(1 0 0);

  /* Border & Input */
  --border: oklch(0.922 0 0);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);             /* Focus rings */

  /* Radius */
  --radius: 0.5rem;
}

Dark Mode (.dark)

.dark {
  --background: oklch(0.145 0 0);       /* Inverted */
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.145 0 0);
  --card-foreground: oklch(0.985 0 0);

  --primary: oklch(0.922 0 0);          /* Adjusted for dark */
  --primary-foreground: oklch(0.205 0 0);

  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);

  --border: oklch(0.269 0 0);
  --input: oklch(0.269 0 0);
}

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:

  1. Lightness (L) - Most important, weight 0.5
  2. Chroma (C) - Saturation, weight 0.3
  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:

// From mock's tailwind.config
const mockTokens = {
  colors: {
    primary: '#137fec',
    'bg-dark': '#101922',
    accent: '#00d4ff'
  }
}

Step 2: Read Theme Tokens

# Extract all CSS variables
grep -E "^\s*--" contents/themes/{theme}/styles/globals.css

Step 3: Create Mapping

For each mock token:

  1. Check exact match (hex → hex)
  2. Check semantic match (primary → --primary)
  3. Calculate color similarity
  4. Flag gaps if no good match

Step 4: Document Gaps

{
  "gaps": [
    {
      "mockValue": "#ff5722",
      "mockUsage": "accent icons",
      "closestToken": "--destructive",
      "similarity": 0.72,
      "recommendation": "USE_CLOSEST or ADD_TOKEN"
    }
  ]
}

Output Format: ds-mapping.json

{
  "theme": "default",
  "themeGlobalsPath": "contents/themes/default/styles/globals.css",
  "analyzedAt": "2025-01-09T12:00:00Z",

  "themeTokens": {
    "colors": {
      "--background": "oklch(1 0 0)",
      "--foreground": "oklch(0.145 0 0)",
      "--primary": "oklch(0.205 0 0)",
      "--secondary": "oklch(0.97 0 0)",
      "--accent": "oklch(0.97 0 0)",
      "--muted": "oklch(0.97 0 0)",
      "--destructive": "oklch(0.577 0.245 27.325)"
    },
    "radius": "0.5rem",
    "fonts": {
      "sans": "var(--font-sans)",
      "mono": "var(--font-mono)"
    }
  },

  "mockTokens": {
    "colors": {
      "primary": "#137fec",
      "background-dark": "#101922",
      "accent": "#00d4ff",
      "text-light": "#ffffff",
      "text-muted": "#94a3b8"
    }
  },

  "colorMapping": [
    {
      "id": "color-1",
      "mockValue": "#137fec",
      "mockName": "primary",
      "mockUsage": ["buttons", "links", "focus rings"],
      "themeToken": "--primary",
      "themeValue": "oklch(0.205 0 0)",
      "tailwindClass": "bg-primary text-primary-foreground",
      "matchType": "semantic",
      "similarity": 0.65,
      "notes": "Theme primary is darker, mock is more vibrant blue"
    },
    {
      "id": "color-2",
      "mockValue": "#101922",
      "mockName": "background-dark",
      "mockUsage": ["hero background", "footer"],
      "themeToken": "--background",
      "themeValue": "oklch(0.145 0 0)",
      "tailwindClass": "bg-background",
      "matchType": "closest",
      "similarity": 0.88,
      "notes": "Use dark mode or bg-gray-900"
    }
  ],

  "typographyMapping": [
    {
      "mockFont": "Inter",
      "themeToken": "--font-sans",
      "tailwindClass": "font-sans",
      "matchType": "exact"
    }
  ],

  "spacingMapping": [
    {
      "mockValue": "24px",
      "tailwindClass": "p-6",
      "matchType": "exact"
    }
  ],

  "radiusMapping": [
    {
      "mockValue": "8px",
      "themeToken": "--radius",
      "themeValue": "0.5rem",
      "tailwindClass": "rounded-lg",
      "matchType": "exact"
    }
  ],

  "gaps": [
    {
      "type": "color",
      "mockValue": "#00d4ff",
      "mockName": "accent",
      "mockUsage": ["terminal prompt", "code highlights"],
      "closestToken": "--primary",
      "similarity": 0.45,
      "recommendations": [
        {
          "option": "A",
          "action": "Use --primary",
          "impact": "Loses cyan accent, uses theme primary"
        },
        {
          "option": "B",
          "action": "Add --accent-cyan to theme",
          "impact": "Requires theme modification"
        },
        {
          "option": "C",
          "action": "Use inline text-[#00d4ff]",
          "impact": "Not recommended, breaks theming"
        }
      ]
    }
  ],

  "summary": {
    "totalMockTokens": 12,
    "mapped": 10,
    "gaps": 2,
    "overallCompatibility": 0.83,
    "recommendation": "PROCEED_WITH_GAPS"
  }
}

Output Format: block-plan.json (BLOCKS workflow only)

For the BLOCKS workflow, an additional output is generated to guide block development:

{
  "mockPath": "mocks/",
  "analyzedAt": "2026-01-12T12:00:00Z",
  "workflow": "BLOCKS",

  "existingBlocks": [
    {
      "name": "hero-simple",
      "similarity": 0.85,
      "matchReason": "Similar layout and components"
    },
    {
      "name": "hero-centered",
      "similarity": 0.72,
      "matchReason": "Centered text, different background"
    }
  ],

  "decision": {
    "type": "new" | "variant" | "existing",
    "blockName": "hero-terminal",
    "baseBlock": "hero-simple",
    "reasoning": "Requires custom terminal animation component not in existing blocks"
  },

  "blockSpec": {
    "name": "hero-terminal",
    "category": "hero",
    "fields": [
      {"name": "title", "type": "text", "required": true},
      {"name": "subtitle", "type": "text", "required": false},
      {"name": "primaryCta", "type": "link", "required": true},
      {"name": "secondaryCta", "type": "link", "required": false},
      {"name": "terminalContent", "type": "textarea", "required": true}
    ],
    "customComponents": ["TerminalAnimation"],
    "estimatedComplexity": "medium"
  },

  "developmentNotes": [
    "Terminal animation requires custom React component",
    "Use existing Button component for CTAs",
    "Background gradient matches theme --background token"
  ]
}

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-theme command
  • 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:

function hexToOklch(hex) {
  // Remove # if present
  hex = hex.replace('#', '')

  // Parse RGB
  const r = parseInt(hex.substr(0, 2), 16) / 255
  const g = parseInt(hex.substr(2, 2), 16) / 255
  const b = parseInt(hex.substr(4, 2), 16) / 255

  // Convert to linear RGB
  const rL = r <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4)
  const gL = g <= 0.04045 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4)
  const bL = b <= 0.04045 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4)

  // Approximate OKLCH
  const L = 0.4122 * rL + 0.5363 * gL + 0.0514 * bL
  const lightness = Math.cbrt(L)

  // Chroma and hue calculation
  const max = Math.max(r, g, b)
  const min = Math.min(r, g, b)
  const chroma = (max - min) * 0.3

  let hue = 0
  if (max !== min) {
    if (max === r) hue = 60 * (((g - b) / (max - min)) % 6)
    else if (max === g) hue = 60 * ((b - r) / (max - min) + 2)
    else hue = 60 * ((r - g) / (max - min) + 4)
    if (hue < 0) hue += 360
  }

  return `oklch(${lightness.toFixed(4)} ${chroma.toFixed(4)} ${hue.toFixed(0)})`
}

Step 2: Generate Dark Mode (Invert Lightness)

For dark mode, invert the lightness (L) value:

function invertLightnessOklch(oklchValue) {
  // Parse oklch(L C H)
  const match = oklchValue.match(/oklch\(([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\)/)
  if (!match) return oklchValue

  const L = parseFloat(match[1])
  const C = parseFloat(match[2])
  const H = parseFloat(match[3])

  // Invert lightness: L' = 1 - L
  const invertedL = 1 - L

  return `oklch(${invertedL.toFixed(4)} ${C.toFixed(4)} ${H.toFixed(0)})`
}

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

/**
 * Theme: {theme}
 * Generated from mock: {mockPath}
 * Date: {timestamp}
 *
 * NOTE: Dark mode was auto-generated by inverting lightness values.
 * Review and adjust .dark {} section as needed for your brand.
 */

:root {
  /* Surface Colors */
  --background: {oklch from mock};
  --foreground: {oklch from mock};
  --card: {oklch from mock or default};
  --card-foreground: {oklch from mock or default};
  --popover: {oklch from mock or default};
  --popover-foreground: {oklch from mock or default};

  /* Interactive Colors */
  --primary: {oklch from mock};
  --primary-foreground: {calculated contrast};
  --secondary: {oklch from mock or default};
  --secondary-foreground: {calculated contrast};
  --accent: {oklch from mock or default};
  --accent-foreground: {calculated contrast};

  /* State Colors */
  --muted: {oklch from mock or default};
  --muted-foreground: {oklch from mock or default};
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(1 0 0);

  /* Border & Input */
  --border: {oklch from mock or default};
  --input: {oklch from mock or default};
  --ring: {oklch from mock or default};

  /* Chart Colors */
  --chart-1: oklch(0.81 0.1 252);
  --chart-2: oklch(0.62 0.19 260);
  --chart-3: oklch(0.55 0.22 263);
  --chart-4: oklch(0.49 0.22 264);
  --chart-5: oklch(0.42 0.18 266);

  /* Sidebar */
  --sidebar: {based on background};
  --sidebar-foreground: {based on foreground};
  --sidebar-primary: {based on primary};
  --sidebar-primary-foreground: {based on primary-foreground};
  --sidebar-accent: {based on accent};
  --sidebar-accent-foreground: {based on accent-foreground};
  --sidebar-border: {based on border};
  --sidebar-ring: {based on ring};

  /* Typography */
  --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

  /* Design Tokens */
  --radius: 0.625rem;
  --spacing: 0.25rem;

  /* Shadows */
  --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
  --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
  --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
  --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
  --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
  --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
  --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
  --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
}

/* =============================================
   DARK MODE (Auto-generated by inverting lightness)
   Review and adjust as needed for your brand
   ============================================= */

.dark {
  --background: {inverted};
  --foreground: {inverted};
  --card: {inverted};
  --card-foreground: {inverted};
  --popover: {inverted};
  --popover-foreground: {inverted};
  --primary: {adjusted for dark};
  --primary-foreground: {adjusted for dark};
  --secondary: {inverted};
  --secondary-foreground: {inverted};
  --muted: {inverted};
  --muted-foreground: {inverted};
  --accent: {inverted};
  --accent-foreground: {inverted};
  --destructive: oklch(0.704 0.191 22.216);
  --destructive-foreground: oklch(0.985 0 0);
  --border: {inverted};
  --input: {inverted};
  --ring: {inverted};
  --sidebar: {inverted};
  --sidebar-foreground: {inverted};
  --sidebar-primary: {adjusted};
  --sidebar-primary-foreground: {adjusted};
  --sidebar-accent: {inverted};
  --sidebar-accent-foreground: {inverted};
  --sidebar-border: {inverted};
  --sidebar-ring: {inverted};
}

/* =============================================
   TAILWIND v4 THEME MAPPING
   ============================================= */

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);

  --font-sans: var(--font-sans);
  --font-mono: var(--font-mono);
  --font-serif: var(--font-serif);

  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);

  --shadow-2xs: var(--shadow-2xs);
  --shadow-xs: var(--shadow-xs);
  --shadow-sm: var(--shadow-sm);
  --shadow: var(--shadow);
  --shadow-md: var(--shadow-md);
  --shadow-lg: var(--shadow-lg);
  --shadow-xl: var(--shadow-xl);
  --shadow-2xl: var(--shadow-2xl);
}

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 patterns
  • shadcn-theming - shadcn/ui theme customization
  • mock-analysis - For extracting mock tokens
  • page-builder-blocks - For applying tokens to blocks
  • block-decision-matrix - For block new/variant/existing decisions
Related skills