Cascade layers settled an argument from 1998
Notes on specificity, and the twenty-five years spent working around it.
Specificity was always the part of CSS that people worked around rather than with. Every methodology of the last two decades — BEM, utility classes, CSS-in-JS scoping — is at bottom a strategy for never having to lose a specificity fight.
The workaround era
The honest description of what most codebases did is: keep every selector at the same weight, so the cascade degenerates into source order, which is a thing humans can actually reason about.
/* one class, always, forever — the whole methodology in one line */
.card__title { font-size: 1.25rem; }
.card__title--muted { color: #7b88a4; }
It worked, and it cost you the ability to express intent. A reset and a component override are conceptually different kinds of rule, and flattening them to the same weight throws that information away.
What layers change
@layer lets you say which bucket a rule belongs to, and the bucket wins before
specificity is consulted at all:
@layer reset, base, components, utilities;
@layer components {
#sidebar .card__title { color: var(--c-ink); }
}
@layer utilities {
.u-muted { color: var(--c-ink-faint); } /* wins anyway */
}
The utility wins despite the ID in the component selector, because layer order
outranks specificity entirely. That single ID would previously have needed 100
points of counter-specificity or an !important, and now it needs nothing.
The part worth noticing
This is the same story as the spacer GIF, one layer up. The methodologies were not wrong; they were a workaround for a missing feature, deployed at enormous scale, for long enough that everyone forgot it was a workaround and started teaching it as architecture.
The interesting question is which of today’s conventions are the same thing, and we will not know for another decade.