- Tailwind v4 + shadcn/ui Production Stack
- Production-tested
-
- WordPress Auditor (
- https://wordpress-auditor.webfonts.workers.dev
- )
- Last Updated
-
- 2026-01-20
- Versions
-
- tailwindcss@4.1.18, @tailwindcss/vite@4.1.18
- Status
- Production Ready ✅
Quick Start (Follow This Exact Order)
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 if exists
rm
tailwind.config.ts
v4 doesn't use this file
- 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"
- :
- ""
- ,
- // ← Empty for v4
- "css"
- :
- "src/index.css"
- ,
- "baseColor"
- :
- "slate"
- ,
- "cssVariables"
- :
- true
- }
- }
- The Four-Step Architecture (MANDATORY)
- Skipping steps will break your theme. Follow exactly:
- Step 1: Define CSS Variables at Root
- / src/index.css /
- @import
- "tailwindcss"
- ;
- @import
- "tw-animate-css"
- ;
- / Required for shadcn/ui animations /
- :root
- {
- --background
- :
- hsl
- (
- 0
- 0
- %
- 100
- %
- )
- ;
- / ← hsl() wrapper required /
- --foreground
- :
- hsl
- (
- 222.2
- 84
- %
- 4.9
- %
- )
- ;
- --primary
- :
- hsl
- (
- 221.2
- 83.2
- %
- 53.3
- %
- )
- ;
- / ... all light mode colors /
- }
- .dark
- {
- --background
- :
- hsl
- (
- 222.2
- 84
- %
- 4.9
- %
- )
- ;
- --foreground
- :
- hsl
- (
- 210
- 40
- %
- 98
- %
- )
- ;
- --primary
- :
- hsl
- (
- 217.2
- 91.2
- %
- 59.8
- %
- )
- ;
- / ... all dark mode colors /
- }
- Critical
-
- Define at root level (NOT inside
- @layer base
- ). Use
- hsl()
- wrapper.
- Step 2: Map Variables to Tailwind Utilities
- @theme
- inline
- {
- --color-background
- :
- var
- (
- --background
- )
- ;
- --color-foreground
- :
- var
- (
- --foreground
- )
- ;
- --color-primary
- :
- var
- (
- --primary
- )
- ;
- / ... map ALL CSS variables /
- }
- Why
-
- Generates utility classes (
- bg-background
- ,
- text-primary
- ). Without this, utilities won't exist.
- Step 3: Apply Base Styles
- @layer
- base
- {
- body
- {
- background-color
- :
- var
- (
- --background
- )
- ;
- / NO hsl() wrapper here /
- 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
- "
- >
- {
- / No dark: variants needed - theme switches automatically /
- }
- </
- div
- >
- Dark Mode Setup
- 1. Create ThemeProvider
- (see
- templates/theme-provider.tsx
- )
- 2. Wrap App
- :
- // src/main.tsx
- import
- {
- ThemeProvider
- }
- from
- '@/components/theme-provider'
- ReactDOM
- .
- createRoot
- (
- document
- .
- getElementById
- (
- 'root'
- )
- !
- )
- .
- render
- (
- <
- ThemeProvider defaultTheme
- =
- "dark"
- storageKey
- =
- "vite-ui-theme"
- >
- <
- App
- /
- >
- <
- /
- ThemeProvider
- >
- )
- 3. Add Theme Toggle
- :
- pnpm
- dlx shadcn@latest
- add
- dropdown-menu
- See
- reference/dark-mode.md
- for ModeToggle component.
- Critical Rules
- ✅ Always Do:
- Wrap colors with
- hsl()
- in
- :root
- /
- .dark
- :
- --bg: hsl(0 0% 100%);
- Use
- @theme inline
- to map all CSS variables
- Set
- "tailwind.config": ""
- in components.json
- Delete
- tailwind.config.ts
- if exists
- Use
- @tailwindcss/vite
- plugin (NOT PostCSS)
- ❌ Never Do:
- Put
- :root
- /
- .dark
- inside
- @layer base
- (causes cascade issues)
- Use
- .dark { @theme { } }
- pattern (v4 doesn't support nested @theme)
- Double-wrap colors:
- hsl(var(--background))
- Use
- tailwind.config.ts
- for theme (v4 ignores it)
- Use
- @apply
- directive (deprecated in v4, see error #7)
- Use
- dark:
- variants for semantic colors (auto-handled)
- Use
- @apply
- with
- @layer base
- or
- @layer components
- classes (v4 breaking change - use
- @utility
- instead) |
- Source
- Wrap ANY styles in
- @layer base
- without understanding CSS layer ordering (see error #8) |
- Source
- Common Errors & Solutions
- This skill prevents
- 8 documented errors
- .
- 1. ❌ tw-animate-css Import Error
- Error
-
- "Cannot find module 'tailwindcss-animate'"
- Cause
- shadcn/ui deprecated
tailwindcss-animate
for v4.
Solution
:
✅ DO
pnpm
add
-D
tw-animate-css
Add to src/index.css:
@import
"tailwindcss"
;
@import
"tw-animate-css"
;
❌ DON'T
npm
install
tailwindcss-animate
v3 only
-
- ❌ Colors Not Working
- Error
- :
- bg-primary
- doesn't apply styles
- Cause
- Missing
@theme inline
mapping
Solution
:
@theme
inline
{
--color-background
:
var
(
--background
)
;
--color-foreground
:
var
(
--foreground
)
;
--color-primary
:
var
(
--primary
)
;
/ ... map ALL CSS variables /
}
-
- ❌ Dark Mode Not Switching
- Error
-
- Theme stays light/dark
- Cause
- Missing ThemeProvider
Solution
:
Create ThemeProvider (see
templates/theme-provider.tsx
)
Wrap app in
main.tsx
Verify
.dark
class toggles on
element
4. ❌ Duplicate @layer base
Error
: "Duplicate @layer base" in console
Cause
: shadcn init adds
@layer base
- don't add another
Solution
:
/* ✅ Correct - single @layer base */
@import
"tailwindcss"
;
:root
{
--background
:
hsl
(
0
0
%
100
%
)
;
}
@theme
inline
{
--color-background
:
var
(
--background
)
;
}
@layer
base
{
body
{
background-color
:
var
(
--background
)
;
}
}
5. ❌ Build Fails with tailwind.config.ts
Error
: "Unexpected config file"
Cause
: v4 doesn't use
tailwind.config.ts
(v3 legacy)
Solution
:
rm
tailwind.config.ts
v4 configuration happens in
src/index.css
using
@theme
directive.
6. ❌ @theme inline Breaks Dark Mode in Multi-Theme Setups
Error
: Dark mode doesn't switch when using
@theme inline
with custom variants (e.g.,
data-mode="dark"
)
Source
:
GitHub Discussion #18560
Cause
:
@theme inline
bakes variable VALUES into utilities at build time. When dark mode changes the underlying CSS variables, utilities don't update because they reference hardcoded values, not variables.
Why It Happens
:
@theme inline
inlines VALUES at build time:
bg-primary
→
background-color: oklch(...)
Dark mode overrides change the CSS variables, but utilities already have baked-in values
The CSS specificity chain breaks
Solution
: Use
@theme
(without inline) for multi-theme scenarios:
/* ✅ CORRECT - Use @theme without inline */
@custom-variant
dark
(
&
:
where
(
[data-mode=dark]
,
[data-mode=dark] *
)
)
;
@theme
{
--color-text-primary
:
var
(
--color-slate-900
)
;
--color-bg-primary
:
var
(
--color-white
)
;
}
@layer
theme
{
[
data-mode
=
"dark"
]
{
--color-text-primary
:
var
(
--color-white
)
;
--color-bg-primary
:
var
(
--color-slate-900
)
;
}
}
When to use inline
:
Single theme + dark mode toggle (like shadcn/ui default) ✅
Referencing other CSS variables that don't change ✅
When NOT to use inline
:
Multi-theme systems (data-theme="blue" | "green" | etc.) ❌
Dynamic theme switching beyond light/dark ❌
Maintainer Guidance
(Adam Wathan):
"It's more idiomatic in v4 for the actual generated CSS to reference your theme variables. I would personally only use inline when things don't work without it."
7. ❌ @apply with @layer base/components (v4 Breaking Change)
Error
:
Cannot apply unknown utility class: custom-button
Source
:
GitHub Discussion #17082
Cause
: In v3, classes defined in
@layer base
and
@layer components
could be used with
@apply
. In v4, this is a breaking architectural change.
Why It Happens
: v4 doesn't "hijack" the native CSS
@layer
at-rule anymore. Only classes defined with
@utility
are available to
@apply
.
Migration
:
/* ❌ v3 pattern (worked) */
@layer
components
{
.custom-button
{
@apply
px-4 py-2 bg-blue-500
;
}
}
/* ✅ v4 pattern (required) */
@utility
custom-button
{
@apply
px-4 py-2 bg-blue-500
;
}
/* OR use native CSS */
@layer
base
{
.custom-button
{
padding
:
1
rem
0.5
rem
;
background-color
:
theme
(
colors.
blue
.500
)
;
}
}
Note
: This skill already discourages
@apply
usage. This error is primarily for users migrating from v3.
8. ❌ @layer base Styles Not Applying
Error
: Styles defined in
@layer base
seem to be ignored
Source
:
GitHub Discussion #16002
|
Discussion #18123
Cause
: v4 uses native CSS layers. Base styles CAN be overridden by utility layers due to CSS cascade if layers aren't explicitly ordered.
Why It Happens
:
v3: Tailwind intercepted
@layer base/components/utilities
and processed them specially
v4: Uses native CSS layers - if you don't import layers in the right order, precedence breaks
Styles ARE being applied, but utilities override them
Solution Option 1
: Define layers explicitly:
@import
"tailwindcss/theme.css"
layer
(
theme
)
;
@import
"tailwindcss/base.css"
layer
(
base
)
;
@import
"tailwindcss/components.css"
layer
(
components
)
;
@import
"tailwindcss/utilities.css"
layer
(
utilities
)
;
@layer
base
{
body
{
background-color
:
var
(
--background
)
;
}
}
Solution Option 2
(Recommended): Don't use
@layer base
- define styles at root level:
@import
"tailwindcss"
;
:root
{
--background
:
hsl
(
0
0
%
100
%
)
;
}
body
{
background-color
:
var
(
--background
)
;
/* No @layer needed */
}
Applies to
: ALL base styles, not just color variables. Avoid wrapping ANY styles in
@layer base
unless you understand CSS layer ordering.
Quick Reference
Symptom
Cause
Fix
bg-primary
doesn't work
Missing
@theme inline
Add
@theme inline
block
Colors all black/white
Double
hsl()
wrapping
Use
var(--color)
not
hsl(var(--color))
Dark mode not switching
Missing ThemeProvider
Wrap app in
Build fails
tailwind.config.ts
exists
Delete file
Animation errors
Using
tailwindcss-animate
Install
tw-animate-css
What's New in Tailwind v4
OKLCH Color Space (December 2024)
Tailwind v4.0 replaced the entire default color palette with OKLCH, a perceptually uniform color space.
Source
:
Tailwind v4.0 Release
|
OKLCH Migration Guide
Why OKLCH
:
Perceptual consistency
: HSL's "50% lightness" is visually inconsistent across hues (yellow appears much brighter than blue at same lightness)
Better gradients
: Smooth transitions without muddy middle colors
Wider gamut
: Supports colors beyond sRGB on modern displays
More vibrant colors
: Eye-catching, saturated colors previously limited by sRGB
Browser Support
(January 2026):
Chrome 111+, Firefox 113+, Safari 15.4+, Edge 111+
Global coverage: 93.1%
Automatic Fallbacks
: Tailwind generates sRGB fallbacks for older browsers:
.bg-blue-500
{
background-color
:
#3b82f6
;
/* sRGB fallback */
background-color
:
oklch
(
0.6
0.24
264
)
;
/* Modern browsers */
}
Custom Colors
: When defining custom colors, OKLCH is now preferred:
@theme
{
/* Modern approach (preferred) */
--color-brand
:
oklch
(
0.7
0.15
250
)
;
/* Legacy approach (still works) */
--color-brand
:
hsl
(
240
80
%
60
%
)
;
}
Migration
: No breaking changes - Tailwind generates fallbacks automatically. For new projects, use OKLCH-aware tooling for custom colors.
Built-in Features (No Plugin Needed)
Container Queries
(built-in as of v4.0):
<
div
className
=
"
@container
"
>
<
div
className
=
"
@md:text-lg @lg:grid-cols-2
"
>
Content responds to container width, not viewport
div
>
div
>
Line Clamp
(built-in as of v3.3):
<
p
className
=
"
line-clamp-3
"
>
Truncate to 3 lines with ellipsis...
p
>
<
p
className
=
"
line-clamp-[8]
"
>
Arbitrary values supported
p
>
<
p
className
=
"
line-clamp-(--teaser-lines)
"
>
CSS variable support
p
>
Removed Plugins
:
@tailwindcss/container-queries
- Built-in now
@tailwindcss/line-clamp
- Built-in since v3.3
Tailwind v4 Plugins
Use
@plugin
directive (NOT
require()
or
@import
):
Typography
(for Markdown/CMS content):
pnpm
add
-D
@tailwindcss/typography
@import
"tailwindcss"
;
@plugin
"@tailwindcss/typography"
;
<
article
class
=
"
prose dark:prose-invert
"
>
{{ content }}
article
>
Forms
(cross-browser form styling):
pnpm
add
-D
@tailwindcss/forms
@import
"tailwindcss"
;
@plugin
"@tailwindcss/forms"
;
Container Queries
(built-in, no plugin needed):
<
div
className
=
"
@container
"
>
<
div
className
=
"
@md:text-lg
"
>
Responds to container width
div
>
div
>
Common Plugin Errors
:
/* ❌ WRONG - v3 syntax */
@import
"@tailwindcss/typography"
;
/* ✅ CORRECT - v4 syntax */
@plugin
"@tailwindcss/typography"
;
Setup Checklist
@tailwindcss/vite
installed (NOT postcss)
vite.config.ts
uses
tailwindcss()
plugin
components.json
has
"config": ""
NO
tailwind.config.ts
exists
src/index.css
follows 4-step pattern:
:root
/
.dark
at root level (not in @layer)
Colors wrapped with
hsl()
@theme inline
maps all variables
@layer base
uses unwrapped variables
ThemeProvider wraps app
Theme toggle works
File Templates
Available in
templates/
directory:
index.css
- Complete CSS with all color variables
components.json
- shadcn/ui v4 config
vite.config.ts
- Vite + Tailwind plugin
theme-provider.tsx
- Dark mode provider
utils.ts
-
cn()
utility
Migration from v3
See
reference/migration-guide.md
for complete guide.
Key Changes
:
Delete
tailwind.config.ts
Move theme to CSS with
@theme inline
Replace
@tailwindcss/line-clamp
(now built-in:
line-clamp-*
)
Replace
tailwindcss-animate
with
tw-animate-css
Update plugins:
require()
→
@plugin
Additional Migration Gotchas
Automated Migration Tool May Fail
Warning
: The
@tailwindcss/upgrade
utility often fails to migrate configurations.
Source
:
Community Reports
|
GitHub Discussion #16642
Common failures
:
Typography plugin configurations
Complex theme extensions
Custom plugin setups
Recommendation
: Don't rely on automated migration. Follow manual steps in the migration guide instead.
Default Element Styles Removed
Tailwind v4 takes a more minimal approach to Preflight, removing default styles for headings, lists, and buttons.
Source
:
GitHub Discussion #16517
|
Medium: Migration Problems
Impact
:
All headings (
through
) render at same size
Lists lose default padding
Visual regressions in existing projects
Solutions
:
Option 1: Use @tailwindcss/typography for content pages
:
pnpm
add
-D
@tailwindcss/typography
@import
"tailwindcss"
;
@plugin
"@tailwindcss/typography"
;
<
article
className
=
"
prose dark:prose-invert
"
>
{
/* All elements styled automatically */
}
article
>
Option 2: Add custom base styles
:
@layer
base
{
h1
{
@apply
text-4
xl
font-bold mb-4
;
}
h2
{
@apply
text-3
xl
font-bold mb-3
;
}
h3
{
@apply
text-2
xl
font-bold mb-2
;
}
ul
{
@apply
list-disc pl-6 mb-4
;
}
ol
{
@apply
list-decimal pl-6 mb-4
;
}
}
PostCSS Setup Complexity
Recommendation
: Use
@tailwindcss/vite
plugin for Vite projects instead of PostCSS.
Source
:
Medium: Migration Problems
|
GitHub Discussion #15764
Why Vite Plugin is Better
:
// ✅ Vite Plugin - One line, no PostCSS config
import
tailwindcss
from
'@tailwindcss/vite'
export
default
defineConfig
(
{
plugins
:
[
react
(
)
,
tailwindcss
(
)
]
,
}
)
// ❌ PostCSS - Multiple steps, plugin compatibility issues
// 1. Install @tailwindcss/postcss
// 2. Configure postcss.config.js
// 3. Manage plugin order
// 4. Debug plugin conflicts
PostCSS Problems Reported
:
Error: "It looks like you're trying to use tailwindcss directly as a PostCSS plugin"
Multiple PostCSS plugins required:
postcss-import
,
postcss-advanced-variables
,
tailwindcss/nesting
v4 PostCSS plugin is separate package:
@tailwindcss/postcss
Official Guidance
: The Vite plugin is recommended for Vite projects. PostCSS is for legacy setups or non-Vite environments.
Visual Changes
Ring Width Default
: Changed from 3px to 1px
Source
:
Medium: Migration Guide
ring
class is now thinner
Use
ring-3
to match v3 appearance
// v3: 3px ring
<
button
className
=
"
ring
"
>
Button
button
>
// v4: 1px ring (thinner)
<
button
className
=
"
ring
"
>
Button
button
>
// Match v3 appearance
<
button
className
=
"
ring-3
"
>
Button
button
>
Reference Documentation
architecture.md
- Deep dive into 4-step pattern
dark-mode.md
- Complete dark mode implementation
common-gotchas.md
- Troubleshooting guide
migration-guide.md
- v3 → v4 migration
Official Documentation
shadcn/ui Vite Setup
:
https://ui.shadcn.com/docs/installation/vite
shadcn/ui Tailwind v4
:
https://ui.shadcn.com/docs/tailwind-v4
Tailwind v4 Docs
:
https://tailwindcss.com/docs
Last Updated
: 2026-01-20
Skill Version
: 3.0.0
Tailwind v4
: 4.1.18 (Latest)
Production
: WordPress Auditor (
https://wordpress-auditor.webfonts.workers.dev
)
Changelog
:
v3.0.0 (2026-01-20): Major research update - added 3 TIER 1 errors (#6-8), expanded migration guide with community findings (TIER 2), added OKLCH color space section, PostCSS complexity warnings, and migration tool limitations
v2.0.1 (2026-01-03): Production verification
v2.0.0: Initial release with 5 documented errors