Tailwind v4 Shadcn
Configures Tailwind v4 with shadcn/ui using a mandatory four-step CSS variable theming to enable automatic dark mode and prevent common errors.
Like a lobster shell, security has layers — review code before you run it.
License
SKILL.md
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:
- Four-step theming architecture (mandatory)
- CSS variable-based color system
- Automatic dark mode switching
- Error prevention for 8 common mistakes
- Migration guide from v3
- Production-ready templates
WHEN
- Starting a new React/Vite project with Tailwind v4
- Migrating from Tailwind v3 to v4
- Setting up shadcn/ui with Tailwind v4
- Debugging: colors not working, dark mode broken, build failures
- Fixing
@theme inline,@apply, or@layer baseissues
KEYWORDS
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 init
# 2. 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
<div className="bg-background text-foreground">
{/* Theme switches automatically via .dark class */}
</div>
No dark: variants needed for semantic colors.
Critical Rules
Always Do
- Wrap colors with
hsl()in:root/.dark:--bg: hsl(0 0% 100%); - Use
@theme inlineto map all CSS variables - Set
"tailwind.config": ""in components.json - Delete
tailwind.config.tsif exists - Use
@tailwindcss/viteplugin (NOT PostCSS)
Never Do
- Put
:root/.darkinside@layer base - Use
.dark { @theme { } }(v4 doesn't support nested @theme) - Double-wrap:
hsl(var(--background)) - Use
tailwind.config.tsfor theme - Use
@applywith@layer base/componentsclasses - Use
dark:variants for semantic colors
Common Errors & Solutions
Error 1: tw-animate-css Import
Error: Cannot find module 'tailwindcss-animate'
# Wrong (v3 package)
npm install tailwindcss-animate
# Correct (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)
<div className="@container">
<div className="@md:text-lg @lg:grid-cols-2">
Content responds to container width
</div>
</div>
Line Clamp (Built-in)
<p className="line-clamp-3">Truncate to 3 lines...</p>
Plugins
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
Migration from v3
Key Changes
- Delete
tailwind.config.ts - Move theme to CSS with
@theme inline - Replace
tailwindcss-animate→tw-animate-css - Replace
require()→@plugin @applyin@layer components→@utility
Color Migration
// Before: Hardcoded + dark variants
<div className="bg-blue-50 dark:bg-blue-950 text-blue-700 dark:text-blue-300">
// After: Semantic + automatic
<div className="bg-info/10 text-info">
Visual Changes
- Ring width default: 3px → 1px (use
ring-3to match v3) - Heading styles removed from Preflight (add via
@tailwindcss/typographyor custom)
Files
templates/index.css- Complete CSS with all variablestemplates/theme-provider.tsx- Dark mode providertemplates/vite.config.ts- Vite configurationtemplates/components.json- shadcn/ui v4 configtemplates/utils.ts-cn()utilityreferences/architecture.md- Deep dive on four-step patternreferences/migration-guide.md- Semantic color migrationreferences/dark-mode.md- Complete dark mode setup
Setup Checklist
-
@tailwindcss/viteinstalled -
vite.config.tsusestailwindcss()plugin -
components.jsonhas"config": "" - NO
tailwind.config.tsexists -
src/index.cssfollows 4-step pattern - ThemeProvider wraps app
- Theme toggle works
NEVER
- Put
:rootor.darkinside@layer base - Use
tailwind.config.tswith v4 (it's ignored) - Double-wrap colors:
hsl(var(--background)) - Use
tailwindcss-animate(usetw-animate-css) - Use
@applyon@layer base/componentsclasses in v4 - Skip the
@theme inlinestep
Files
14 totalComments
Loading comments…
