SuperAPI AI Directory

tailwind-errors-build-failures

Frontend & websites

View repository

Updated 10-15 22:18

About this skill

Tailwind Build Failures : Why Your Classes Don't Render

The Tailwind compiler emits ONLY the utility classes it can DETECT in your source files. Every "Tailwind is broken" report is one of :

  1. The compiler did not see the file containing the class.
  2. The compiler saw the file but could not parse the class as a complete unbroken string.
  3. The class was detected, compiled, but a stale build cache served yesterday's CSS to the browser.
  4. A version-specific regression in v4 or Turbopack swallowed certain class patterns silently.

This skill is the diagnostic flow for all four. Always go top to bottom : root-cause-first.

Companion skills :

  • tailwind-impl-config-v3 : the content array surface
  • tailwind-impl-config-v4 : the @source directive surface
  • tailwind-impl-build-vite : Vite-specific cache behaviour
  • tailwind-impl-build-postcss-cli : PostCSS / CLI flow
  • tailwind-errors-runtime : when classes ARE in CSS but not applied
  • tailwind-errors-v4-migration : v3-to-v4 specific breakage

Quick Reference : 60-Second Diagnostic

1. Inspect compiled CSS (open the file the browser loads).
   - File is EMPTY                   -> scanner found nothing
   - File has SOME classes, missing X -> X was not detected
   - File looks right but browser shows old styles -> cache stale
   - File is missing a recently added class -> watcher missed it

2. Search compiled CSS for the missing class :
   grep "p-4" dist/output.css

3. If absent : the SCANNER missed it. Go to "Scanner Diagnosis".
   If present : the BUILD is stale OR another rule wins. Go to
   "Cache Diagnosis" + tailwind-errors-runtime.

Decision Tree : Scanner Missed the Class

Is the class an UNBROKEN STRING in source ?
  ├── NO  (string interpolation: `bg-${color}-500`)
  │   -> FIX: replace with static lookup (see "Static-Class Discipline")
  │
  └── YES
      ├── v3 : Is the file inside `content` globs ?
      │   ├── NO  -> add the glob OR widen existing one
      │   └── YES -> check next branch
      │
      └── v4 : Is the file inside auto-detect scope OR explicit @source ?
          ├── NO (file in node_modules, monorepo sibling, etc.)
          │   -> Add @source "../path/to/file"
          └── YES
              ├── Turbopack ? -> see "Turbopack Workaround"
              ├── Astro v4.0.8 ? -> pin to v4.0.7
              └── Otherwise -> restart watcher (new-directory case)

Decision Tree : Stale Cache

Did the class appear in CSS yesterday but not today after a code change ?

Vite project ?
  -> rm -rf node_modules/.vite ; npm run dev

Next.js project ?
  -> rm -rf .next ; npm run dev

Webpack project ?
  -> Disable persistent cache OR rm -rf node_modules/.cache

Astro project ?
  -> rm -rf .astro dist node_modules/.vite ; npm run dev

CLI watch mode ?
  -> Ctrl-C, restart npx (@tailwindcss/cli OR tailwindcss)

CRITICAL : Static-Class Discipline

90% of "Tailwind is broken" reports trace to ONE pattern.

Wrong (works in browser console, fails in build)

function Button({ color, size }) {
  return <button className={`bg-${color}-600 text-${size}`}>...</button>;
}

At compile time, Tailwind sees the strings bg-, -600, text- but NEVER the complete bg-blue-600. The class never enters compiled CSS.

Right (complete class strings)

function Button({ color, size }: { color: "blue" | "red"; size: "sm" | "lg" }) {
  const colorClasses = {
    blue: "bg-blue-600 hover:bg-blue-700",
    red: "bg-red-600 hover:bg-red-700",
  };
  const sizeClasses = {
    sm: "text-sm",
    lg: "text-lg",
  };
  return <button className={`${colorClasses[color]} ${sizeClasses[size]}`}>...</button>;
}

Every class appears as a complete unbroken string somewhere in source. The scanner finds them.

Right (safelist when dynamic is unavoidable)

v3 :

// tailwind.config.js
module.exports = {
  safelist: [
    "bg-red-500",
    {
      pattern: /bg-(red|green|blue)-(100|200|300|400|500)/,
      variants: ["hover", "focus"],
    },
  ],
};

v4 :

@import "tailwindcss";
@source inline("{hover:,focus:,}bg-{red,green,blue}-{100,200,300,400,500}");

The brace expansion in @source inline is the v4 equivalent of v3's safelist pattern.

v3 Content Array : The Survival Kit

Minimum

// tailwind.config.js
module.exports = {
  content: ["./src/**/*.{html,js,jsx,ts,tsx,vue,svelte}"],
};

Common mistakes (and fixes)

Symptom Cause Fix
New app/admin/ directory unstyled Pre-existing glob ./src/** does not include app/ Add "./app/**/*.{js,ts,jsx,tsx}"
Classes from a CMS-rendered HTML missing HTML files not in content Add "./public/**/*.html" or wherever HTML lives
Classes in MDX files missing .mdx not in extension list Append mdx : "./content/**/*.{md,mdx}"
Tailwind picks up node_modules (build slow) Glob too broad (./**/*) Scope to specific dirs
Classes from imported @my-org/ui package missing The package's source not in content Add "./node_modules/@my-org/ui/**/*.{js,jsx,ts,tsx}"

Safelist for truly-dynamic strings

safelist: [
  "bg-red-500", "bg-green-500", "bg-blue-500",
  { pattern: /^col-span-\d+$/, variants: ["sm", "md", "lg"] },
],

Blocklist to suppress accidental classes

blocklist: ["container"],   // strip 'container' if you defined your own

v4 @source Directive : The Survival Kit

Default auto-detection scope

  • All files in current working directory, recursively.
  • IGNORED : files matched by .gitignore, node_modules, binary files, CSS files, package-manager lock files.

Adding a monorepo sibling

@import "tailwindcss";
@source "../packages/ui/src";
@source "../packages/marketing/src";

Adding a node_modules package

@import "tailwindcss";
@source "../../node_modules/@my-org/ui";

Setting a different base path

@import "tailwindcss" source("../src");

Excluding a path

@source not "../src/legacy";

Disabling auto-detection entirely

@import "tailwindcss" source(none);
@source "../src/components";
@source "../src/pages";

Use this for monorepos where you want EXPLICIT control.

Inline safelist with brace expansion

@source inline("underline");
@source inline("{hover:,focus:,}underline");
@source inline("{hover:,}bg-red-{50,{100..900..100},950}");

The {n..m..step} range is a v4 feature. Pre-generates the entire palette for dynamic class needs.

CRITICAL : Stale Build Cache

Tailwind itself is fast and deterministic. The host bundler's cache is what goes stale.

Vite + Tailwind v4

The Vite dependency-cache lives in node_modules/.vite. When you edit tailwind.config.js or app.css, Vite sometimes serves cached PostCSS output. Symptom : you delete bg-red-500 from source, it still appears in the browser.

rm -rf node_modules/.vite
npm run dev

Next.js (App Router + Pages Router)

Next.js caches PostCSS output in .next/. After upgrading Tailwind or switching v3-v4, force a fresh build :

rm -rf .next
npm run dev

Astro

Astro layers its own .astro cache on top of Vite's .vite cache :

rm -rf .astro dist node_modules/.vite
npm run dev

Standalone CLI watch

Kill the watcher (Ctrl-C) and restart. The watcher's chokidar (v3) or Oxide (v4) caches file lists at startup ; new-directory additions need a restart.

Known Version-Specific Regressions

Turbopack arbitrary-value miss (Next.js 16.x + Tailwind v4.2.1)

Per issue 19825 : aspect-[12/5], z-[100], and similar arbitrary-value classes are emitted in markup but NEVER reach compiled CSS when using Next.js 16.1.6 with --turbo. Affects files that import certain third-party libraries (e.g. Swiper.js).

Workarounds :

# Option 1 : disable Turbopack (use Webpack)
next dev    # no --turbo
// Option 2 : inline style instead of arbitrary value
<div style={{ aspectRatio: "12/5" }}>
<div style={{ zIndex: 100 }}>
// Option 3 : add the arbitrary class to @source inline so it
//           is always generated regardless of detection
@source inline("aspect-[12/5] z-[100]");

Astro v4.0.8 regression (issue 16733)

Per issue 16733 : Tailwind v4.0.8 with @tailwindcss/vite v4.0.8 and Astro v5.3.0 silently drops styles from imported component packages.

Workaround : pin to v4.0.7 until upstream resolves.

{
  "dependencies": {
    "tailwindcss": "4.0.7",
    "@tailwindcss/vite": "4.0.7"
  }
}

Lock both packages in package-lock.json / yarn.lock to prevent auto-bump.

CRITICAL : v3-v4 Directive Mix-Up

Project Entry CSS line Result
v3 @import "tailwindcss"; Empty CSS, browser fetches URL
v3 @tailwind base; @tailwind components; @tailwind utilities; Works
v4 @tailwind base; ... Deprecation warnings + partial output
v4 @import "tailwindcss"; Works

Detection : if the compiled CSS is <2 KB, the directive line is probably wrong.

CRITICAL : Watcher Misses New Directory

# v3
mkdir src/admin
# Watcher does NOT pick up files in src/admin if content glob is
# ./src/components/**/*  rather than  ./src/**/*

# v4
mkdir packages/widget
# Auto-detect picks it up if it's in cwd ; if it's a sibling outside
# cwd, ADD @source "../widget".

Fix : restart watcher AND widen content/@source scope.

What This Skill Does NOT Cover

  • Classes ARE in CSS but browser shows wrong styles -> see tailwind-errors-runtime (specificity, cascade, scope).
  • v3-to-v4 migration breakage -> see tailwind-errors-v4-migration.
  • Forms / plugin classes missing -> see tailwind-impl-plugins-official § "Verifying a Plugin Loaded".
  • cn/twMerge returns unexpected output -> see tailwind-impl-tailwind-merge.
  • HMR not updating React state (not a Tailwind issue) -> see the bundler's docs.

References

  • references/methods.md : every config option, every directive, every cache-invalidation command.
  • references/examples.md : full diagnostic walk-throughs for the classic failure modes.
  • references/anti-patterns.md : the traps catalogued by symptom.

Official Source Links