Tailwind CSS in Production · Missing and Wrong Styles

The Badge That Lost Its Colour

Status badges rendered grey in production because their colour classes were built from string fragments that Tailwind's scanner never saw.

Create a free account to save this page, take notes and track your progress. Sign in

Scenario

Example team: an order dashboard shows a coloured badge per status. A developer refactors the badge into a component that takes a color prop and builds the class name: bg-${color}-100 text-${color}-700. It looks fine locally, where an old build still has the classes cached.

What you see in production

After the next deploy every badge is plain grey text. Nothing is wrong in the HTML: the classes are there (bg-amber-100), but the CSS file contains no rule for them.

The naive code

function Badge({ color, children }) {
  return <span className={`rounded-full px-2 py-0.5 bg-${color}-100 text-${color}-700`}>{children}</span>;
}

Reproduce it

Tailwind generates CSS for class names it finds as plain text in your source files. A small model of that scan:

What the class scanner can seeTry it yourself
import re

source = """
function Badge({ color, children }) {
  return <span className={`rounded-full px-2 py-0.5 bg-${color}-100 text-${color}-700`}>{children}</span>;
}
const PAID = "bg-green-100 text-green-700";   // a complete class string
"""

candidates = set(re.findall(r"[\w:/.\-\[\]]+", source))
for wanted in ["bg-amber-100", "text-amber-700", "bg-green-100", "text-green-700", "rounded-full"]:
    print(f"{wanted:15} {'found -> CSS generated' if wanted in candidates else 'NOT found -> no CSS'}")
print("the scanner only sees fragments like:", sorted(c for c in candidates if c.startswith(("bg-", "text-"))))

Output

bg-amber-100    NOT found -> no CSS
text-amber-700  NOT found -> no CSS
bg-green-100    found -> CSS generated
text-green-700  found -> CSS generated
rounded-full    found -> CSS generated
the scanner only sees fragments like: ['bg-', 'bg-green-100', 'text-', 'text-green-700']

Root cause

Tailwind doesn't run your JavaScript. It reads files as text and keeps anything that looks like a class. bg-${color}-100 is split into fragments, so bg-amber-100 is never generated unless it appears in full somewhere else.

The fix

Map each variant to complete class names:

Badges from a complete-class mapTry it yourself
<!-- const STATUS = { paid: "bg-green-100 text-green-700", pending: "bg-amber-100 text-amber-700", overdue: "bg-red-100 text-red-700" } -->
<div class="flex gap-2 p-6">
  <span class="rounded-full bg-green-100 px-2.5 py-0.5 text-sm font-medium text-green-700">Paid</span>
  <span class="rounded-full bg-amber-100 px-2.5 py-0.5 text-sm font-medium text-amber-700">Pending</span>
  <span class="rounded-full bg-red-100 px-2.5 py-0.5 text-sm font-medium text-red-700">Overdue</span>
</div>

If values really come from data (a CMS or database), safelist them in CSS: @source inline("bg-{red,amber,green}-100");.

Trade-offs

  • A mapping object is a little more code, but it's searchable, type-checkable and documents the allowed variants.
  • Safelisting adds CSS even when unused; keep the list small.

Detect it in production

  • Visual regression tests (screenshots) of each component variant in CI.
  • A lint rule against template literals in className (for example with eslint-plugin-tailwindcss or a custom rule).

Key points

  • Tailwind finds classes by scanning source text; build-time, not runtime.
  • Never assemble class names from fragments; map to complete strings.
  • Safelist with @source inline() only for genuinely dynamic data.

Practice questions

Why does className={`bg-${color}-500`} produce no styles in a production build?

Answer

Tailwind scans source files as plain text and only generates CSS for class names written in full. The interpolated string never contains bg-red-500, so no rule is generated.

How do you safelist classes that come from a database?

Answer

In v4, add @source inline("…") to your CSS (brace expansion is supported, e.g. bg-{red,green}-100). In v3 it was the safelist option in tailwind.config.js.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?