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
:rootCSS variables for theme values in use. - base: Preflight, the reset built on modern-normalize: margins removed,
box-sizing: border-box, borders set to0 solid, images block-level, headings unstyled. - components: empty by default; your own component classes go here via
@layer components { ... }. - utilities: every generated utility and
@utilityyou 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.