D
P
0

CSS & Tailwind

Tailwind v4 Tree-Shook My CSS Variable → Text Shipped Invisible White in Production

June 7, 2026·4 min read
Tailwind v4 Tree-Shook My CSS Variable → Text Shipped Invisible White in Production

This bug is special because it never showed up locally — only in production. On a site I built, the small eyebrow text above the hero headline was supposed to be gold. On my dev machine it was gold, perfect. Once it was built and deployed, the text shipped white — practically invisible against a light background. No error, no build warning, nothing failed. The text just… disappeared visually.

After digging in, the cause was this: Tailwind v4 tree-shook my CSS variable out of the compiled CSS. The variable resolved to nothing, and color fell back to inheriting #fff from the parent.

Why the variable disappeared

In Tailwind v4, the tokens you define in @theme do not automatically make it into the final CSS. Tailwind only emits an @theme variable to the compiled CSS if some Tailwind utility actually consumes it. If no utility uses that token, it gets tree-shaken — treated as dead code.

My setup looked roughly like this:

@theme {
  --color-gold: oklch(0.78 0.09 85);
  --color-gold-light: oklch(0.86 0.06 85); /* the -light variant */
}

Then I colored the eyebrow text by referencing the variable directly:

.hero-eyebrow {
  color: var(--color-gold-light);
}

The problem: there was never a utility like text-gold-light used anywhere in the markup. I only ever used the variable in hand-written CSS. From Tailwind's point of view, --color-gold-light was never consumed by any utility, so it didn't make it into the production output.

Locally it often goes unnoticed because the dev server sometimes includes more tokens, or the rebuild order differs. In the production build, tree-shaking is aggressive: --color-gold-light vanishes from the CSS. So var(--color-gold-light) resolves to an empty value, color becomes invalid, and the element inherits color: #fff from the hero container above it. Gold → white.

The diagnostic that proves it instantly: grep the compiled CSS to see which --color-* tokens actually made it in.

# See which custom properties actually landed in the production output
grep -o '\-\-color-[a-z-]*' dist/assets/*.css | sort -u
# --color-gold  present
# --color-gold-light  MISSING  → tree-shaken out

The moment --color-gold-light is absent from the grep results despite being in the source, it's confirmed: Tailwind dropped it at build time.

The same variant: an arbitrary value that never compiled

I also got bitten by another flavor of the same bug with an arbitrary class. I wrote something like text-[oklch(0.96_0.02_85/0.80)], expecting Tailwind to produce a selector for that color. What happened: the class never compiled, and the text stayed invisible.

The cause: an arbitrary class only generates an exact-string selector if that exact string was scanned at build time. Tailwind also canonicalizes values (for example /0.80 becomes /0.8), so the generated selector and the class in the HTML can fail to match — an alpha of /0.80 versus /0.8 is enough to miss, and the rule never matches.

The fix: use stable tokens, or give a fallback

There are three ways to fix it, and I pick whichever fits the context:

1. Use a utility token, not the raw variable. If you use text-gold-light in the markup, Tailwind guarantees the token gets emitted:

<p class="hero-eyebrow text-gold-light">In conversation</p>

Because the utility is genuinely used, --color-gold-light will never be tree-shaken.

2. Give var() a fallback. Even if the variable is dropped, the fallback keeps the color showing:

.hero-eyebrow {
  color: var(--color-gold-light, #d4c49b);
}

3. Hardcode the hex for one-off values that don't need to live in the token system:

.hero-eyebrow {
  color: #d4c49b;
}

I lean on option 1 for colors that are part of the design system, and option 2 as a cheap safety net anywhere I reference a theme var in hand-written CSS.

A few extra notes

  • The mental model for Tailwind v4: @theme defines tokens, but a token only reaches production if a utility consumes it. Referencing var(--color-x) in hand-written CSS does not count as consumption.
  • Make that grep step part of your build verification: quickly check which --color-* actually exist in the compiled CSS. A five-second check beats an hour of guessing.
  • Be wary of arbitrary classes for brand colors. Value canonicalization (/0.80/0.8, whitespace normalization) makes them fragile. A named token is far more predictable.
  • "White in production, correct locally" almost always means a custom property resolved to nothing and then inherited the default text color. Follow the inheritance chain up until you find the missing var.

The takeaway: in Tailwind v4, a CSS variable is no longer something that's automatically present — its existence depends on whether the build believes the variable is used. Once you treat a token as something that must "earn its place" through a utility or be protected by a fallback, this whole class of "invisible text in production" bugs goes away.