Specificity fights are expensive because every fix raises the next override’s cost. Cascade layers give teams another lever: explicit order.
Without layers, a reset, component rule, utility class, and page override all compete in the same author origin. The later or more specific rule wins. With layers, you can decide that utilities always beat components, even when the utility selector is simpler.
Define the order once
@layer reset, tokens, base, components, utilities, overrides;
That line is the contract. Rules in later layers win over rules in earlier layers when origin and importance are the same.
@layer components {
.button {
background: var(--button-bg);
color: var(--button-text);
}
}
@layer utilities {
.bg-warning {
background: var(--color-warning);
}
}
The utility can stay simple. It does not need .button.bg-warning to win.
Layers are not a license to hide chaos
Bad selector habits are still bad inside a layer. If a component rule needs IDs, deep descendant chains, or repeated :not() selectors, the layer will not make it maintainable. Layers are most useful when paired with low-specificity component APIs.
Keep unlayered CSS intentional
Unlayered author styles have higher priority than layered styles. That can be surprising if a random legacy file sits outside the layer system. In a greenfield codebase, put all authored CSS into layers or reserve unlayered rules for a very small integration boundary.
A practical layer stack
For most sites, this order is enough:
@layer reset, tokens, base, layout, components, utilities;
Resets normalize behavior. Tokens define values. Base styles handle elements. Layout defines page primitives. Components style reusable UI. Utilities provide deliberate final adjustments.
The payoff is not cleverness. The payoff is deleting a class without wondering which selector war it was fighting.