← Volver a la lista

Why Tailwind v4 Utility Classes Lose to Shared CSS

csstailwind

I had a custom class in a monorepo's shared CSS (say .tool-card { padding: ... }) and tried to override it in an app with pl-10 — nothing happened. Same specificity, one class each, yet the shared CSS kept winning.

The cause: cascade layers

Tailwind v4 emits its utilities inside @layer utilities. Meanwhile, an ordinary class written in a shared CSS file belongs to no layer at all (it is unlayered).

Per the CSS cascade layers spec, unlayered styles beat all layered styles. Specificity is only compared between rules in the same layer, so no matter how late pl-10 (inside a layer) loads, it can never beat the unlayered .tool-card padding.

/* Shared CSS (unlayered) → always wins */
.tool-card {
  padding-left: 1.5rem;
}

/* Generated by Tailwind (inside @layer utilities) → loses */
@layer utilities {
  .pl-10 {
    padding-left: 2.5rem;
  }
}

What to do

Three options.

  1. Override with an inline style: style={{ paddingLeft: "2.5rem" }}. Inline styles win regardless of layers. For a pinpoint override this is the quickest path
  2. Move the shared CSS into @layer components { ... }: being declared before the utilities layer, it becomes overridable by utilities. Requires restructuring the shared package, though
  3. Stop designing for overrides: don't have shared classes and utilities fight over the same property in the first place

If the shared CSS can't easily be layered, in practice you end up with option 1, the inline style.

Summary

  • Tailwind v4 utilities live in @layer utilities → they always lose to plain unlayered CSS
  • It is not a specificity problem; it is the cascade layers spec
  • For pinpoint overrides, inline styles are the reliable tool