Skip to content

Fix: Tailwind CSS Classes Not Applying — Styles Missing in Production or Development

FixDevs ·

Quick Answer

How to fix Tailwind CSS not applying styles — content config paths, JIT mode, dynamic class names, PostCSS setup, CDN vs build tool, and purging issues.

The Problem

Tailwind CSS classes are in the HTML but styles aren’t applied:

<!-- Classes written correctly but have no effect -->
<div class="bg-blue-500 text-white p-4 rounded-lg">
  Hello World
</div>
<!-- Renders as unstyled text -->

Or styles work in development but disappear in the production build:

npm run build   # Production build
# All custom Tailwind classes removed — only base styles remain

Or dynamically constructed class names don’t work:

// Dynamic class name — doesn't work
const color = 'blue';
const size = '500';
const className = `bg-${color}-${size}`;   // bg-blue-500 — NOT generated

Or after upgrading from Tailwind v2 to v3, many classes stopped working.

Why This Happens

Tailwind CSS uses a purge/scan step to generate only the CSS classes actually used in your codebase. This keeps bundle sizes small but causes classes to disappear if the scanner can’t find them:

  • content paths not configured — Tailwind v3 requires a content array in tailwind.config.js specifying which files to scan for class names. Missing or wrong paths mean Tailwind doesn’t see your class usage.
  • Dynamic class names — Tailwind scans for complete class name strings. bg-${color}-500 is never seen as bg-blue-500 — the scanner finds the template literal but can’t evaluate it.
  • Incorrect PostCSS setup — Tailwind runs as a PostCSS plugin. Missing @tailwind directives in your CSS or a broken PostCSS config causes no output.
  • CSS file not imported — your Tailwind CSS file must be imported in your JavaScript entry point.
  • Tailwind v2 vs v3 differences — v3 uses JIT (Just-In-Time) by default. Some class names changed.

Fix 1: Configure content Paths Correctly

The content array tells Tailwind where to look for class names. Every file type that contains Tailwind classes must be included:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    // React / Next.js
    './src/**/*.{js,jsx,ts,tsx}',
    './pages/**/*.{js,jsx,ts,tsx}',
    './components/**/*.{js,jsx,ts,tsx}',
    './app/**/*.{js,jsx,ts,tsx}',

    // Vue
    './src/**/*.{vue,js,ts}',

    // HTML files
    './**/*.html',
    '!./node_modules/**',  // Exclude node_modules

    // If classes come from external libraries
    './node_modules/your-component-lib/**/*.{js,ts}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

Verify your config is finding files:

# Use npx tailwindcss CLI to debug what's being scanned
npx tailwindcss --content './src/**/*.{js,jsx}' --output /dev/null --verbose
# Lists all files being scanned

Common content path mistakes:

// WRONG — missing file extensions
content: ['./src/**/*']   // Matches all files but Tailwind needs to know the type

// WRONG — path doesn't match project structure
content: ['./pages/**/*.tsx']   // But files are in ./src/pages/ — nothing found

// WRONG — quotes inside path are wrong
content: ["./src/**/*.{js,jsx,ts,tsx}"]   // Fine — but curly braces need no escaping

// CORRECT — explicit extensions, correct paths
content: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html']

Fix 2: Add Required CSS Directives

Your CSS entry file must include the three Tailwind directives:

/* src/index.css or src/globals.css */
@tailwind base;       /* Resets and base element styles */
@tailwind components; /* Component classes (e.g., from plugins) */
@tailwind utilities;  /* Utility classes (bg-blue-500, p-4, etc.) */

Import the CSS in your entry point:

// src/main.js or src/index.js (React/Vue)
import './index.css';   // Must import the CSS file with @tailwind directives

// src/app/layout.tsx (Next.js App Router)
import './globals.css';

For Tailwind v4 (new CSS-first config):

/* Tailwind v4 uses @import instead of @tailwind directives */
@import "tailwindcss";

/* Config in CSS, not JS */
@theme {
  --color-primary: oklch(70% 0.2 240);
}

Fix 3: Fix Dynamic Class Names

Tailwind can only generate classes that appear as complete strings in scanned files:

// WRONG — template literal — Tailwind can't evaluate at scan time
const colorClass = `bg-${color}-500`;    // bg-blue-500 NOT generated

// WRONG — string concatenation
const cls = 'bg-' + color + '-500';      // NOT generated

// WRONG — computed object key
const classes = { [`text-${size}`]: true };   // NOT generated

// CORRECT — complete class names must exist as strings somewhere
// Option 1 — use a mapping object with complete class names
const colorMap = {
  blue: 'bg-blue-500',
  red: 'bg-red-500',
  green: 'bg-green-500',
};
const cls = colorMap[color];   // 'bg-blue-500' exists as a string — generated

// Option 2 — conditional with complete class names
const cls = color === 'blue' ? 'bg-blue-500' : 'bg-red-500';

// Option 3 — safelist in tailwind.config.js
// For class names you can't write as complete strings
// tailwind.config.js — safelist specific classes or patterns
module.exports = {
  content: ['./src/**/*.{js,jsx}'],
  safelist: [
    'bg-blue-500',
    'bg-red-500',
    'bg-green-500',
    // Pattern-based safelist
    {
      pattern: /bg-(red|blue|green)-(400|500|600)/,
      variants: ['hover', 'dark'],  // Include hover and dark mode variants
    },
  ],
};

Common Mistake: Many developers discover their dynamic classes “work in development but break in production.” This is because the development server may use a different purging strategy (or no purging). In production, only classes found in the content scan are generated.

Fix 4: Verify PostCSS Configuration

Tailwind requires PostCSS. Check the setup:

// postcss.config.js — must include tailwindcss plugin
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

// Or with explicit paths (if config file not in root)
module.exports = {
  plugins: {
    tailwindcss: { config: './tailwind.config.js' },
    autoprefixer: {},
  },
};

Verify packages are installed:

npm list tailwindcss postcss autoprefixer

# If missing:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p   # Creates tailwind.config.js and postcss.config.js

Check for PostCSS version conflicts:

# PostCSS 8 is required for Tailwind v3+
npm list postcss
# Should show [email protected]

# If an older version is installed:
npm install -D postcss@latest

Fix 5: Tailwind v3 JIT Mode Differences from v2

Tailwind v3 made JIT the default and removed some v2 behaviors:

// tailwind.config.js v2 — needed explicit JIT mode
module.exports = {
  mode: 'jit',   // Had to opt in
  purge: ['./src/**/*.{js,jsx}'],   // Was 'purge', now 'content'
};

// tailwind.config.js v3 — JIT is default, 'purge' renamed to 'content'
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],   // 'content' not 'purge'
  // No 'mode' needed — JIT is always on
};

Classes that changed in v3:

<!-- v2 — overflow-ellipsis, overflow-clip -->
<p class="overflow-ellipsis">...</p>

<!-- v3 — renamed to text-ellipsis, text-clip -->
<p class="text-ellipsis">...</p>

<!-- v2 — decoration-slice, decoration-clone (didn't exist) -->
<!-- v3 — box-decoration-slice, box-decoration-clone -->

<!-- v2 — flex-grow-0, flex-shrink-0 -->
<div class="flex-grow-0 flex-shrink-0">...</div>

<!-- v3 — grow-0, shrink-0 -->
<div class="grow-0 shrink-0">...</div>

Arbitrary values (v3 JIT feature):

<!-- JIT allows arbitrary values with [] syntax -->
<div class="w-[372px] bg-[#1a1a2e] text-[14px] mt-[7px]">
  Custom sizes without config
</div>

<!-- v2 without JIT: had to add custom values in tailwind.config.js -->
<!-- v3 JIT: use [] for any one-off value -->

Fix 6: Dark Mode and Responsive Classes

Variant classes require correct configuration:

// tailwind.config.js — dark mode configuration
module.exports = {
  darkMode: 'class',   // Dark mode triggered by .dark class on <html>
  // or 'media'        // Dark mode follows prefers-color-scheme media query
};
<!-- Dark mode class variant -->
<div class="bg-white dark:bg-gray-900 text-black dark:text-white">
  Works with darkMode: 'class' and <html class="dark">
</div>

<!-- Responsive variants — mobile-first -->
<div class="w-full md:w-1/2 lg:w-1/3">
  Full width on mobile, half on md, third on lg
</div>

<!-- Hover, focus, active states -->
<button class="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 focus:ring-2">
  Button
</button>

Verify dark mode is applied to the HTML element:

// Toggle dark mode
document.documentElement.classList.toggle('dark');
// or
document.documentElement.classList.add('dark');

Fix 7: Debug Missing Classes

Find out why a specific class isn’t generated:

# Check if a class is in the generated CSS
npm run build
grep "bg-blue-500" dist/assets/index-*.css
# If not found — class wasn't detected during content scan

# Run Tailwind CLI manually to test
npx tailwindcss -i ./src/index.css -o ./output.css --watch
# Watch mode shows files being scanned

# Test with a minimal HTML file
echo '<div class="bg-blue-500 p-4">test</div>' > test.html

# Add test.html to content:
# content: ['./test.html']
# Then check if bg-blue-500 appears in output

Browser DevTools — check if the class is defined:

1. Open DevTools → Elements → select the element
2. Check Computed styles — if bg-blue-500 isn't there, it wasn't generated
3. Filter Styles panel for "background" — if empty, class was purged
4. Check if there's a CSS specificity conflict overriding Tailwind

Check for CSS specificity conflicts:

/* External CSS overriding Tailwind */
.card {
  background-color: white !important;   /* Overrides bg-blue-500 */
}

/* Fix — use Tailwind's important modifier */
/* In HTML: class="!bg-blue-500" */
/* Or configure important in tailwind.config.js */
// tailwind.config.js — make all utilities important
module.exports = {
  important: true,   // Adds !important to all utilities
  // or
  important: '#app', // Scope to specific element ID
};

Still Not Working?

Vite + Tailwind — Vite uses its own asset pipeline. Ensure tailwindcss is in postcss.config.js (not just in vite.config.js). Vite processes CSS through PostCSS automatically if the config file exists.

Create React App (CRA) — CRA uses its own PostCSS setup. Add Tailwind via @craco/craco or react-app-rewired to customize PostCSS, or use the CRACO Tailwind plugin.

Class conflicts from base styles@tailwind base resets many default browser styles. If elements look wrong even with correct Tailwind classes, the base reset may be interfering. Use @layer base to customize base styles.

Fonts and custom properties — Tailwind’s built-in font families (font-sans, font-mono) reference CSS custom properties. If your CSS resets --font-family, Tailwind fonts may not apply.

For related CSS issues, see Fix: CSS Animation Not Working and Fix: CSS Variable Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles