npx skills add ...
npx skills add jezweb/claude-skills --skill tailwind-theme-builder
Set up Tailwind v4 + shadcn/ui themed UI with dark mode. Install deps, configure CSS variables via @theme inline, wire dark mode toggle, verify. Use whenever the user mentions Tailwind v4, setting up Tailwind theming, shadcn/ui colours, dark mode, or troubleshooting colours not working, tw-animate-css errors, @theme inline conflicts, @apply breaking after upgrade, or v3 → v4 migration issues.
npx skills add jezweb/claude-skills --skill tailwind-theme-builder
Set up a fully themed Tailwind v4 + shadcn/ui project with dark mode. Produces configured CSS, theme provider, and working component library.
Tailwind v4 requires a specific architecture for CSS variable-based theming. This pattern is mandatory -- skipping or modifying steps breaks the theme.
Dark mode switching:
--primary not --blue-500--primary + --primary-foreground)@theme inline mapping, reference via var(--chart-1) in style propsCopy assets/vite.config.ts or add the Tailwind plugin:
This exact order is required. Skipping steps breaks the theme.
src/index.css:
Result: bg-background, text-primary etc. work automatically. Dark mode switches via .dark class -- no dark: variants needed for semantic colours.
Copy assets/theme-provider.tsx to your components directory, then wrap your app:
Add a theme toggle -- install the dropdown menu then use the ModeToggle component below:
"config": "" is critical -- v4 doesn't use tailwind.config.ts.
Always:
hsl() in :root/.dark@theme inline to map all CSS variables@tailwindcss/vite plugin (NOT PostCSS)tailwind.config.ts if it existsNever:
:root/.dark inside @layer base.dark { @theme { } } (v4 doesn't support nested @theme)hsl(var(--background))@apply with @layer base classes (use @utility instead)| # | Symptom | Cause | Fix |
|---|---|---|---|
| 1 | Variables ignored / theme broken | :root inside @layer base | Move :root and .dark to root level |
| 2 | Dark mode colours not switching | .dark { @theme { } } | Use CSS variables + single @theme inline |
| 3 | Colours all black/white | Double hsl() wrapping | Use var(--background) not hsl(var(...)) |
| 4 | bg-primary not generated | Colours in tailwind.config.ts | Delete config, use @theme inline |
| 5 | bg-background class missing | No @theme inline block | Add @theme inline mapping variables |
| 6 | shadcn components break | components.json has config path | Set "config": "" (empty string) |
| 7 | Tailwind not processing | Using PostCSS plugin | Switch to @tailwindcss/vite plugin |
| 8 | @/ imports fail | Missing path aliases | Add paths to tsconfig.app.json |
| 9 | Redundant dark: variants | Using dark:bg-primary-dark | Just use bg-primary -- variables handle it |
| 10 | Hardcoded colours everywhere | Using bg-blue-600 dark:bg-blue-400 | Use semantic tokens: bg-primary |
| 11 | Class merging bugs | String concatenation for classes | Use cn() from @/lib/utils |
| 12 | Radix Select crashes | Empty string value value="" | Use value="placeholder" |
| 13 | Wrong Tailwind version | Installed tailwindcss@^3 | Install tailwindcss@^4.1.0 + @tailwindcss/vite |
| 14 | Missing peer deps | Only installed tailwindcss | Also install clsx, tailwind-merge, @types/node |
| 15 | Broken in dark mode | Only tested light mode | Test light, dark, system, and toggle transitions |
| 16 | Fails WCAG contrast | Looks fine visually | Check ratios: 4.5:1 normal text, 3:1 large/UI |
| 17 | Build fails on animation import | Using tailwindcss-animate (deprecated) | Use tw-animate-css or native CSS animations |
| 18 | CSS priority issues | Duplicate @layer base after shadcn init | Merge into single @layer base block |
#1 -- :root inside @layer base
Tailwind v4 strips CSS outside @theme/@layer, but :root must be at root level to persist. This is the most common setup failure.
WRONG:
CORRECT:
#2 -- Nested @theme
Tailwind v4 does not support @theme inside selectors. Use CSS variables in :root/.dark with a single @theme inline block.
WRONG:
CORRECT:
#3 -- Double hsl() wrapping
Variables already contain hsl(). Double-wrapping creates hsl(hsl(...)).
WRONG: background-color: hsl(var(--background));
CORRECT: background-color: var(--background);
#4 -- Colours in tailwind.config.ts
Tailwind v4 completely ignores theme.extend.colors in config files. Delete the file or leave it empty. Set "config": "" in components.json.
#5 -- Missing @theme inline
Without @theme inline, Tailwind has no knowledge of your CSS variables. Utility classes like bg-background simply won't be generated.
WRONG:
CORRECT:
#7 -- PostCSS vs Vite plugin
WRONG:
CORRECT:
#8 -- Path aliases
Add to tsconfig.app.json:
#11 -- cn() utility for class merging
WRONG: className={`base ${isActive && 'active'}`}
CORRECT: className={cn("base", isActive && "active")}
cn() from @/lib/utils properly merges and deduplicates Tailwind classes.
#12 -- Radix Select empty value
Radix UI Select does not allow empty string values. Use value="placeholder" instead of value="".
#14 -- Required dependencies
#17 -- tw-animate-css
tailwindcss-animate is deprecated in Tailwind v4. shadcn/ui docs may still reference it. Causes build failures and import errors. Use tw-animate-css or @tailwindcss/motion instead.
#18 -- Duplicate @layer base after shadcn init
shadcn init adds its own @layer base block. Check src/index.css immediately after running init and merge any duplicate blocks into one.
WRONG:
CORRECT:
tailwind.config.ts file (or it's empty)components.json has "config": ""hsl() wrapper in :root@theme inline maps all variables@layer base doesn't wrap :rootCopy from assets/ directory:
index.css -- Complete CSS with all colour variablescomponents.json -- shadcn/ui v4 configvite.config.ts -- Vite + Tailwind plugintheme-provider.tsx -- Dark mode providerutils.ts -- cn() utilityreferences/migration-guide.md -- v3 to v4 migration