shadcn-theming
shadcn/ui theme customization for NextSpark applications. Covers tweakcn.com integration, CSS variable format, color space conversion, and theme initialization. Use this skill when initializing or cus
Files1
View on GitHubSKILL.md.claude/skills/shadcn-theming/SKILL.md
GitHubshadcn Theming Skill
Patterns for customizing shadcn/ui themes in NextSpark using CSS variables and the tweakcn.com editor.
Fundamental Principle
PROJECT STYLES ARE THE SINGLE SOURCE OF TRUTH FOR DESIGN TOKENS.
All visual customization happens in the project's styles/globals.css. No inline colors, no hardcoded values.
styles/globals.css → imported by the generated root layout (src/app/layout.tsx) → App
When to Use This Skill
- Initializing a new project's design system
- Customizing colors for a client project
- Converting design system from mock to theme
- Understanding the theme build process
Architecture Overview
THEME CSS STRUCTURE:
styles/
├── globals.css # CSS variables (THE DESIGN SYSTEM)
│ ├── :root # Light mode tokens
│ ├── .dark # Dark mode tokens
│ └── @theme inline # Tailwind v4 mappings
│
└── components.css # Component-specific styles (optional)
There is no theme build step: `nextspark prepare` generates the root layout with
`import '@/styles/globals.css'`, and Next.js compiles it (hot reload in dev).
`components.css` is only loaded if `globals.css` imports it.
Using tweakcn.com
Step 1: Access the Editor
Navigate to: https://tweakcn.com/editor/theme
The editor provides:
- Visual color picker with OKLCH support
- Light/Dark mode preview
- Real-time component preview
- Export in CSS format
Step 2: Customize Your Theme
Base Colors:
- Background/Foreground
- Primary/Secondary
- Accent/Muted
- Destructive
Component Colors:
- Card surfaces
- Popover/Dropdown
- Sidebar (if using dashboard)
Design Tokens:
- Border radius
- Shadows
- Typography (font families)
Step 3: Export CSS
Click "Copy CSS" or "Export" to get:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.55 0.2 250);
--primary-foreground: oklch(0.985 0 0);
/* ... all variables */
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
/* ... dark mode overrides */
}
CSS Variable Reference
Required Variables (All Must Be Defined)
:root {
/* === SURFACE COLORS === */
--background: oklch(L C H); /* Page background */
--foreground: oklch(L C H); /* Primary text */
--card: oklch(L C H); /* Card surfaces */
--card-foreground: oklch(L C H); /* Card text */
--popover: oklch(L C H); /* Dropdown/popover bg */
--popover-foreground: oklch(L C H);
/* === INTERACTIVE COLORS === */
--primary: oklch(L C H); /* Primary buttons, links */
--primary-foreground: oklch(L C H); /* Text on primary */
--secondary: oklch(L C H); /* Secondary buttons */
--secondary-foreground: oklch(L C H);
--accent: oklch(L C H); /* Hover highlights */
--accent-foreground: oklch(L C H);
/* === STATE COLORS === */
--muted: oklch(L C H); /* Muted backgrounds */
--muted-foreground: oklch(L C H); /* Placeholder text */
--destructive: oklch(L C H); /* Error/danger */
--destructive-foreground: oklch(L C H);
/* === BORDER & INPUT === */
--border: oklch(L C H); /* Border color */
--input: oklch(L C H); /* Input border */
--ring: oklch(L C H); /* Focus ring */
/* === CHART COLORS === */
--chart-1: oklch(L C H);
--chart-2: oklch(L C H);
--chart-3: oklch(L C H);
--chart-4: oklch(L C H);
--chart-5: oklch(L C H);
/* === SIDEBAR (if dashboard) === */
--sidebar: oklch(L C H);
--sidebar-foreground: oklch(L C H);
--sidebar-primary: oklch(L C H);
--sidebar-primary-foreground: oklch(L C H);
--sidebar-accent: oklch(L C H);
--sidebar-accent-foreground: oklch(L C H);
--sidebar-border: oklch(L C H);
--sidebar-ring: oklch(L C H);
/* === TYPOGRAPHY === */
--font-sans: ui-sans-serif, system-ui, ...;
--font-serif: ui-serif, Georgia, ...;
--font-mono: ui-monospace, SFMono-Regular, ...;
/* === DESIGN TOKENS === */
--radius: 0.625rem; /* Base border radius */
--spacing: 0.25rem; /* Base spacing unit */
/* === SHADOWS === */
--shadow-2xs: ...;
--shadow-xs: ...;
--shadow-sm: ...;
--shadow: ...;
--shadow-md: ...;
--shadow-lg: ...;
--shadow-xl: ...;
--shadow-2xl: ...;
}
Dark Mode Override
.dark {
/* Only override colors that change in dark mode */
/* Typically invert backgrounds/foregrounds */
--background: oklch(0.145 0 0); /* Dark bg */
--foreground: oklch(0.985 0 0); /* Light text */
--card: oklch(0.205 0 0); /* Slightly lighter */
--card-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0); /* Often lighter in dark mode */
--primary-foreground: oklch(0.205 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--border: oklch(0.275 0 0);
--input: oklch(0.325 0 0);
/* Destructive typically stays similar hue but adjusted */
--destructive: oklch(0.704 0.191 22);
/* Chart colors usually stay the same */
}
Tailwind v4 Theme Mapping
REQUIRED: Map CSS variables to Tailwind for class-based styling.
@theme inline {
/* Colors */
--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);
/* Typography */
--font-sans: var(--font-sans);
--font-mono: var(--font-mono);
--font-serif: var(--font-serif);
/* Radius */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* Shadows */
--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);
}
Color Space: OKLCH
shadcn/ui uses OKLCH color space for perceptually uniform colors.
OKLCH Format
oklch(Lightness Chroma Hue)
- Lightness: 0-1 (0 = black, 1 = white)
- Chroma: 0-0.4 (0 = gray, higher = more saturated)
- Hue: 0-360 (color angle on color wheel)
Common Colors in OKLCH
| Color | OKLCH | Notes |
|---|---|---|
| White | oklch(1 0 0) |
L=1, no chroma |
| Black | oklch(0 0 0) |
L=0 |
| Pure Gray | oklch(0.5 0 0) |
Mid gray, no chroma |
| Blue Primary | oklch(0.55 0.2 250) |
Typical blue |
| Red Destructive | oklch(0.577 0.245 27) |
shadcn default red |
| Green Success | oklch(0.6 0.2 145) |
Typical green |
| Orange Warning | oklch(0.7 0.2 65) |
Typical orange |
Converting HEX to OKLCH
Use online converters or JavaScript:
// Using culori library
import { oklch, formatCss } from 'culori'
const hex = '#137fec'
const oklchColor = oklch(hex)
// { mode: 'oklch', l: 0.55, c: 0.2, h: 250 }
const cssValue = `oklch(${oklchColor.l.toFixed(4)} ${oklchColor.c.toFixed(4)} ${oklchColor.h?.toFixed(4) || 0})`
// "oklch(0.5500 0.2000 250.0000)"
DS Initialization Workflow
Path 1: From tweakcn.com (Recommended)
# 1. Customize at tweakcn.com/editor/theme
# 2. Copy the CSS export
# 3. Run the command with the CSS file
/theme:design-system path/to/exported-theme.css
The command will:
- Parse the CSS file
- Validate required variables
- Add missing
@theme inlinemappings if needed - Write to project's
globals.css - Let Next.js recompile it (
pnpm devreloads; there is no build script to run)
Path 2: From Mock Analysis
Use how-to:customize-theme command with a mock folder:
# Extract DS from a mock design
how-to:customize-theme --from-mock path/to/mock/folder
The process will:
- Read the
design-systemskill for patterns - Extract color palette from mock's Tailwind config
- Convert HEX to OKLCH format
- Generate dark mode by inverting lightness
- Write to project's
globals.css - Let Next.js recompile it (
pnpm devreloads; there is no build script to run)
How the Styles Load
After modifying globals.css, nothing has to be rebuilt: the root layout that
nextspark prepare generates in src/app/layout.tsx imports @/styles/globals.css
directly, so pnpm dev hot-reloads it and pnpm build compiles it. Keep project
assets in public/ (served from /).
Validation Checklist
Before finalizing theme customization:
- All required CSS variables defined in
:root - All dark mode overrides in
.dark -
@theme inlinesection present with all mappings - Colors use OKLCH format (not HEX or HSL)
- Font stacks include fallbacks
-
--radiususes rem units -
pnpm buildcompiles the styles without errors - Light mode looks correct
- Dark mode looks correct
- Components render properly
Common Patterns
Brand Color Integration
:root {
/* Brand blue as primary */
--primary: oklch(0.55 0.2 250); /* #137fec equivalent */
--primary-foreground: oklch(0.985 0 0); /* White text on blue */
/* Brand blue tint for secondary */
--secondary: oklch(0.95 0.03 250); /* Very light blue */
--secondary-foreground: oklch(0.25 0 0); /* Dark text */
/* Brand blue for accent */
--accent: oklch(0.92 0.05 250); /* Light blue hover */
--accent-foreground: oklch(0.25 0 0);
}
Dark Theme with Color Preservation
.dark {
/* Keep brand color vibrant in dark mode */
--primary: oklch(0.65 0.2 250); /* Slightly lighter */
--primary-foreground: oklch(0.1 0 0); /* Dark text on light primary */
/* Muted versions for backgrounds */
--secondary: oklch(0.25 0.03 250); /* Dark blue-gray */
--secondary-foreground: oklch(0.95 0 0); /* Light text */
}
Anti-Patterns
/* ❌ NEVER: Use HEX or HSL in globals.css */
--primary: #137fec;
--primary: hsl(210, 100%, 50%);
/* ✅ CORRECT: Use OKLCH */
--primary: oklch(0.55 0.2 250);
/* ❌ NEVER: Skip dark mode */
/* (Will look terrible in dark mode) */
/* ✅ CORRECT: Define complete dark mode */
.dark {
--background: oklch(0.145 0 0);
/* ... all overrides */
}
/* ❌ NEVER: Skip @theme inline */
/* (Tailwind classes won't work) */
/* ✅ CORRECT: Include complete mapping */
@theme inline {
--color-background: var(--background);
/* ... all mappings */
}
/* ❌ NEVER: Edit anything under src/app/ (generated; overwritten by nextspark prepare) */
/* ✅ CORRECT: Edit styles/globals.css */
Related Skills
tailwind-theming- Tailwind CSS patterns and buildSectionClassesdesign-system- Token mapping from mocksshadcn-components- Component usage patterns