Fix: Tailwind classes not applying — 6 real causes
Tailwind utility classes render in HTML but styles don't apply. Here are the real causes and the exact fix for each, covering both Tailwind v3 and v4.
You added class="bg-blue-500 text-white p-4 rounded-lg" to a button, hard-refreshed, and got a plain unstyled button. Tailwind is installed, the CSS file loads (200 OK in Network tab), yet nothing applies. Here are the six real causes and how to fix each.
Important — v3 vs v4: Tailwind CSS v4 (released early 2025) changed configuration fundamentally — @import "tailwindcss" replaces @tailwind directives, content detection is automatic (no content: [] for most projects), and @theme lives in CSS instead of a JS config. Both versions are widely deployed. Each cause below flags v3-specific vs v4-specific fixes.
Diagnose first
Confirm the problem is Tailwind, not your class:
<div style="background: red; padding: 20px;">If this is red, CSS works</div>
<div class="bg-red-500 p-5">If THIS is red, Tailwind works</div>
If the first div is red but second isn’t, Tailwind is the culprit — dig into the six causes below.
Check what Tailwind actually generated:
grep -c "bg-red-500" ./dist/assets/*.css
If count is 0, Tailwind never generated that class — jump to cause 1 or 2. If it’s 1+ but not applying, jump to cause 3 or later.
Which version are you on?
cat package.json | grep tailwindcss
tailwindcss@^3→ v3, usestailwind.config.js+@tailwinddirectivestailwindcss@^4→ v4, uses@import "tailwindcss"+ auto content detection
Cause 1 — Source files not being scanned
Tailwind scans files to decide which utility classes to include. If your file isn’t in the scan set, the classes never get generated.
Symptom: classes work in one file but not another. Or Tailwind CSS is tiny (~10KB) when it should be 30-50KB in dev.
Fix — v3 (tailwind.config.js content array):
cat tailwind.config.js
Common misses:
src/**/*.jsxbut you’re writing.tsxpages/**/*.jsbut files live inapp/**/*.tsx- Astro’s
.astrofiles not listed .mdxcontent not included
Expand the content array:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{astro,html,js,jsx,ts,tsx,vue,md,mdx}',
'./pages/**/*.{astro,html,js,jsx,ts,tsx,vue,md,mdx}',
'./components/**/*.{astro,html,js,jsx,ts,tsx,vue}',
'./layouts/**/*.{astro,html,js,jsx,ts,tsx,vue}',
],
// ...
}
Fix — v4 (automatic content detection):
v4 detects most template files automatically — no content array needed. If a file is outside the detected roots (monorepo, external UI library, unusual template), add explicit @source directives in your CSS:
@import "tailwindcss";
@source "../path/to/templates";
@source "../node_modules/@my-company/ui-lib";
Restart dev server. Missing classes now appear in the output.
Cause 2 — Dynamic class names (Tailwind can’t detect them)
Tailwind’s scanner is a regex, not a JavaScript interpreter. Constructed class names fail in BOTH v3 and v4:
// Tailwind can't see 'bg-blue-500' or 'bg-red-500'
const color = 'blue';
<div className={`bg-${color}-500`}>
Symptom: the class exists in the DOM but not in the compiled CSS.
Fix — write the full class name as a literal string in the source:
// Full class names Tailwind can see
const colorClass = active ? 'bg-blue-500' : 'bg-red-500';
<div className={colorClass}>
For truly dynamic values, use a safelist (both versions support this, syntax differs slightly):
// v3 — tailwind.config.js
module.exports = {
safelist: [
'bg-blue-500',
'bg-red-500',
{ pattern: /bg-(red|green|blue)-(100|500|900)/ },
],
}
For v4, prefer including the classes in a real template file (in a hidden component or comment) rather than a safelist — Tailwind v4’s philosophy leans harder toward automatic detection.
Prefer literals over safelist — smaller bundle, less runtime coupling.
Cause 3 — CSS import missing or in wrong file
Tailwind directives must reach the CSS pipeline. If the file with those directives isn’t loaded by your app, nothing renders.
Symptom: even simple classes like bg-red-500 don’t work.
Fix — v3 main CSS file:
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Fix — v4 main CSS file:
/* src/index.css */
@import "tailwindcss";
That single line replaces the three v3 directives.
Both versions — check the app imports the CSS:
// main.jsx or _app.tsx or entry file
import './index.css';
Without the import, Tailwind’s CSS never reaches the browser regardless of what’s compiled.
Cause 4 — Specificity conflict with existing CSS
Existing CSS with higher specificity or !important wins over Tailwind utilities:
/* This kills your Tailwind classes */
button {
background: gray !important;
}
Diagnose in DevTools:
- Right-click your element → Inspect
- Look at the “Styles” panel
- If your Tailwind class is crossed out, specificity is the issue
Fix — one of:
- Remove or scope the conflicting CSS
- Use Tailwind’s
!modifier:class="!bg-blue-500"outputsbackground: blue !important - Prefix Tailwind classes with a higher-specificity selector via config
This behaves the same in v3 and v4.
Cause 5 — Build pipeline not processing the CSS
If the build pipeline isn’t set up correctly, Tailwind’s directives are left as literal text in the output CSS and browsers ignore them.
Symptom: view source of the generated CSS — it starts with @tailwind base; or @import "tailwindcss" unchanged instead of expanded utility classes.
Fix — v3 (PostCSS-based):
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Fix — v4 (Vite plugin, preferred):
Install @tailwindcss/vite and register it:
// vite.config.js
import tailwindcss from '@tailwindcss/vite'
export default {
plugins: [tailwindcss()],
}
v4 also supports a PostCSS plugin (@tailwindcss/postcss) for pipelines that require PostCSS.
Astro:
- Astro 4 + Tailwind v3 →
@astrojs/tailwindintegration - Astro 5 + Tailwind v4 → use
@tailwindcss/vitedirectly inastro.config.mjs:
// astro.config.mjs — Astro 5 + Tailwind v4
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
vite: { plugins: [tailwindcss()] },
})
Restart dev server. Compiled CSS should now contain expanded utility classes.
Cause 6 — Cached output from a previous build
.next, .astro, dist, or a service worker cached the old CSS without your new classes.
Fix — nuke every cache and rebuild:
rm -rf .next .astro dist node_modules/.cache node_modules/.vite
Also in the browser:
- DevTools → Application → Service Workers → Unregister
- DevTools → Application → Storage → Clear site data
- Hard refresh: Ctrl+Shift+R (or Cmd+Shift+R on Mac)
Upgrading from v3 to v4
If you’re on v3 and want to switch, Tailwind provides an automated migration tool:
npx @tailwindcss/upgrade
Run on a clean branch. It rewrites your config, CSS imports, and renamed utility classes. Review the diff before committing.
The universal Tailwind debug flow
Every “class not applying” case, in order:
# 1. Sanity check — does inline style work?
# (verify CSS pipeline is alive)
# 2. Grep the compiled CSS
grep -c "your-class-name" ./dist/**/*.css
# 3. Check content scan config (v3 content array or v4 @source directives)
# 4. Check for dynamic class construction in source
# 5. Verify CSS import in entry file (@tailwind directives v3, @import "tailwindcss" v4)
# 6. DevTools Styles panel — specificity conflict?
# 7. Nuke caches + hard refresh
Ninety percent of cases resolve at step 2-4.
Prevention
For new Tailwind projects:
- Complete content scan — v3: content globs. v4: rely on auto-detection plus
@sourcefor edge locations. - No dynamic class construction — always write full literal class names
- Single CSS entry file — one place with the Tailwind directives/import, imported once
- Reset conflicting styles — normalize tags Tailwind doesn’t control
- CI check — build the production CSS, fail if size is < 5KB (means most utilities got purged incorrectly)
- Prettier plugin —
prettier-plugin-tailwindcsssorts classes for consistency
Bottom line
Tailwind classes not applying almost always trace to one of six causes: scan-set misconfiguration, dynamic class names, missing CSS import, specificity conflict, build pipeline setup, or cached output. Start with grep on the compiled CSS — if the class isn’t there, it’s a scanning problem (causes 1-2). If it’s there but not applied, it’s a delivery problem (causes 3-6). The version difference (v3 vs v4) mainly changes HOW you fix the scanning and import problems, not what causes them.
DevOps YAML Pack
36 production-ready configs — Kubernetes, Docker Compose, GitHub Actions, Terraform, Helm, Ansible. Every file heavily commented. Copy, paste, ship.
Get the pack — ₹499 →