Layers, Preflight and specificity

Tailwind CSS · Expert · 12 min

Understanding the generated CSS explains every 'why doesn't my class win?' question in Tailwind projects.

Tailwind v4 emits native cascade layers in this order: @layer theme, base, components, utilities;.

  • theme: the :root CSS variables for theme values in use.
  • base: Preflight, the reset built on modern-normalize: margins removed, box-sizing: border-box, borders set to 0 solid, images block-level, headings unstyled.
  • components: empty by default; your own component classes go here via @layer components { ... }.
  • utilities: every generated utility and @utility you define.

Because layer order beats specificity, any utility overrides any component class, however specific its selector. That's why class="btn px-8" reliably gets px-8.

Unlayered CSS beats all layers. Custom CSS written after the import without a @layer wrapper overrides utilities, which surprises people migrating from v3. Wrap such styles in a layer, typically components.

Utilities are mostly single-class selectors with equal specificity, so among utilities, generated order decides. Tailwind sorts them deliberately: shorthand before longhand (px-4 after p-2 so it wins), and variants after base utilities, with breakpoints ordered small to large.

The important modifier ends with !: bg-red-500! adds !important. And @import "tailwindcss" important; marks all utilities important, for embedding into pages with aggressive existing CSS.

v4 also registers some internal custom properties with @property (with types and initial values), which lets things like gradient stops and shadow colours compose and animate correctly.

Example

/* app.css */
@import "tailwindcss";

@layer components {
  .btn { padding: 0.5rem 1rem; border-radius: 0.375rem; }  /* utilities still win */
}

/* UNLAYERED: beats every Tailwind utility, often by accident */
.card p { margin: 0; }          /* now "mt-4" on those <p> elements does nothing */

/* Safer: put overrides in a layer */
@layer base {
  h1 { font-size: var(--text-3xl); font-weight: 700; }
}

/* Markup:
   <button class="btn px-8">       px-8 wins over .btn's padding (later layer)
   <p class="text-gray-500 text-red-600!">  important modifier forces red */

Remember

  • Layer order: theme, base, components, utilities
  • Utilities beat component classes regardless of specificity
  • Unlayered custom CSS beats all utilities: wrap it in a layer
  • The ! suffix (bg-red-500!) adds !important

Watch out for

  • Writing unlayered custom CSS in v4 and finding utility classes no longer override it.

Check yourself

  1. Custom CSS '.card p { margin: 0 }' is written after @import "tailwindcss" without @layer. What happens to <p class="mt-4"> inside .card?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: tailwindcss.com/docs/adding-custom-styles#adding-base-styles