cascade.md1 min read

← index

Cascade layers settled an argument from 1998

Notes on specificity, and the twenty-five years spent working around it.

1 min

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.