Tailwind v4 Shadcn
by @wpank
Configures Tailwind v4 with shadcn/ui using a mandatory four-step CSS variable theming to enable automatic dark mode and prevent common errors.
clawhub install tailwind-v4-shadcnπ About This Skill
name: tailwind-v4-+-shadcn/ui-stack model: fast
Tailwind v4 + shadcn/ui Stack
Production-tested setup for Tailwind v4 with shadcn/ui. Prevents 8 documented errors through a mandatory four-step architecture.
WHAT
Complete Tailwind v4 + shadcn/ui configuration:
WHEN
@theme inline, @apply, or @layer base issuesKEYWORDS
tailwind v4, tailwindcss 4, shadcn, shadcn/ui, @theme inline, dark mode, css variables, vite, tw-animate-css, tailwind config, migration
Production verified: WordPress Auditor (https://wordpress-auditor.webfonts.workers.dev) Versions: tailwindcss@4.1.18, @tailwindcss/vite@4.1.18
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install tailwind-v4-shadcn
Quick Start
# 1. Install dependencies
pnpm add tailwindcss @tailwindcss/vite
pnpm add -D @types/node tw-animate-css
pnpm dlx shadcn@latest init2. Delete v3 config (v4 doesn't use it)
rm tailwind.config.ts
vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: { alias: { '@': path.resolve(__dirname, './src') } }
})
components.json (CRITICAL):
{
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true
}
}
The Four-Step Architecture (MANDATORY)
Skipping steps breaks theming. Follow exactly:
Step 1: Define CSS Variables at Root
/* src/index.css */
@import "tailwindcss";
@import "tw-animate-css";:root {
--background: hsl(0 0% 100%);
--foreground: hsl(222.2 84% 4.9%);
--primary: hsl(221.2 83.2% 53.3%);
--primary-foreground: hsl(210 40% 98%);
/* ... all light mode colors with hsl() wrapper */
}
.dark {
--background: hsl(222.2 84% 4.9%);
--foreground: hsl(210 40% 98%);
--primary: hsl(217.2 91.2% 59.8%);
--primary-foreground: hsl(222.2 47.4% 11.2%);
/* ... all dark mode colors */
}
Critical: Define at root level (NOT inside @layer base). Use hsl() wrapper.
Step 2: Map Variables to Tailwind
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
/* ... map ALL CSS variables */
}
Why: Generates utility classes (bg-background, text-primary). Without this, utilities don't exist.
Step 3: Apply Base Styles
@layer base {
body {
background-color: var(--background);
color: var(--foreground);
}
}
Critical: Reference variables directly. Never double-wrap: hsl(var(--background)).
Step 4: Result - Automatic Dark Mode
{/* Theme switches automatically via .dark class */}
No dark: variants needed for semantic colors.
Critical Rules
Always Do
1. Wrap colors withhsl() in :root/.dark: --bg: hsl(0 0% 100%);
2. Use @theme inline to map all CSS variables
3. Set "tailwind.config": "" in components.json
4. Delete tailwind.config.ts if exists
5. Use @tailwindcss/vite plugin (NOT PostCSS)Never Do
1. Put:root/.dark inside @layer base
2. Use .dark { @theme { } } (v4 doesn't support nested @theme)
3. Double-wrap: hsl(var(--background))
4. Use tailwind.config.ts for theme
5. Use @apply with @layer base/components classes
6. Use dark: variants for semantic colorsCommon Errors & Solutions
Error 1: tw-animate-css Import
Error: Cannot find module 'tailwindcss-animate'
# Wrong (v3 package)
npm install tailwindcss-animateCorrect (v4 package)
pnpm add -D tw-animate-css
@import "tailwindcss";
@import "tw-animate-css";
Error 2: Colors Not Working
Error: bg-primary doesn't apply styles
Cause: Missing @theme inline mapping
@theme inline {
--color-primary: var(--primary);
/* Map ALL variables */
}
Error 3: Dark Mode Not Switching
Cause: Missing ThemeProvider
See templates/theme-provider.tsx and wrap your app.
Error 4: Build Fails
Error: Unexpected config file
rm tailwind.config.ts # v4 doesn't use this
Error 5: @theme inline Breaks Multi-Theme
Cause: @theme inline bakes values at build time
Use @theme (without inline) for multi-theme systems:
/* For multi-theme (not just light/dark) */
@theme {
--color-text-primary: var(--color-slate-900);
}@layer theme {
[data-theme="dark"] {
--color-text-primary: var(--color-white);
}
}
Error 6: @apply Breaking
Error: Cannot apply unknown utility class
v4 changed @apply behavior:
/* Wrong (v3 pattern) */
@layer components {
.custom-button { @apply px-4 py-2; }
}/* Correct (v4 pattern) */
@utility custom-button {
@apply px-4 py-2;
}
Error 7: @layer base Styles Ignored
Cause: CSS layer cascade issues
/* Better: Don't use @layer base for critical styles */
body {
background-color: var(--background);
}
Quick Reference
| Symptom | Cause | Fix |
|---------|-------|-----|
| bg-primary doesn't work | Missing @theme inline | Add mapping |
| Colors black/white | Double hsl() | Use var(--color) not hsl(var(--color)) |
| Dark mode stuck | Missing ThemeProvider | Wrap app |
| Build fails | tailwind.config.ts exists | Delete file |
| Animation errors | Wrong package | Use tw-animate-css |
Tailwind v4 New Features
OKLCH Color Space
v4 uses OKLCH for perceptually uniform colors. Automatic sRGB fallbacks generated.
@theme {
/* Modern approach */
--color-brand: oklch(0.7 0.15 250);
/* Legacy (still works) */
--color-brand: hsl(240 80% 60%);
}
Container Queries (Built-in)
Content responds to container width
Line Clamp (Built-in)
Truncate to 3 lines...
Plugins
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
Migration from v3
Key Changes
1. Delete tailwind.config.ts
2. Move theme to CSS with @theme inline
3. Replace tailwindcss-animate β tw-animate-css
4. Replace require() β @plugin
5. @apply in @layer components β @utility
Color Migration
// Before: Hardcoded + dark variants
// After: Semantic + automatic
Visual Changes
Ring width default: 3px β 1px (use ring-3 to match v3)
Heading styles removed from Preflight (add via @tailwindcss/typography or custom)
Files
templates/index.css - Complete CSS with all variables
templates/theme-provider.tsx - Dark mode provider
templates/vite.config.ts - Vite configuration
templates/components.json - shadcn/ui v4 config
templates/utils.ts - cn() utility
references/architecture.md - Deep dive on four-step pattern
references/migration-guide.md - Semantic color migration
references/dark-mode.md - Complete dark mode setup
Setup Checklist
[ ] @tailwindcss/vite installed
[ ] vite.config.ts uses tailwindcss() plugin
[ ] components.json has "config": ""
[ ] NO tailwind.config.ts exists
[ ] src/index.css follows 4-step pattern
[ ] ThemeProvider wraps app
[ ] Theme toggle works
NEVER
Put :root or .dark inside @layer base
Use tailwind.config.ts with v4 (it's ignored)
Double-wrap colors: hsl(var(--background))
Use tailwindcss-animate (use tw-animate-css)
Use @apply on @layer base/components classes in v4
Skip the @theme inline step
π‘ Examples
# 1. Install dependencies
pnpm add tailwindcss @tailwindcss/vite
pnpm add -D @types/node tw-animate-css
pnpm dlx shadcn@latest init2. Delete v3 config (v4 doesn't use it)
rm tailwind.config.ts
vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: { alias: { '@': path.resolve(__dirname, './src') } }
})
components.json (CRITICAL):
{
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true
}
}