TT Lab
Get started
Learn Learning paths Courses

CSS — The Later Rule Wins

Start Using Layers and the Sort Grows a Step

Continue in TT Lab

In one line

In the cascade's sort order, layers are decided before specificity. So in a stylesheet that has started using layers, it is perfectly normal for a rule with lower specificity to beat one with higher specificity.

Why this was needed

The path by which CSS falls apart is almost always the same. To beat someone else's rule you write your selector one step longer, the next person writes it even longer, and eventually !important shows up. Then that property can only be handled with !important forever.

The root of the problem is that "the order in which things should win" and "specificity" are different things to begin with. A reset rule should always be the weakest, and a utility class should always be the strongest. But CSS had no way to say that directly, so until now it was imitated with an indirect means: selector length.

@layer is the syntax that writes that order directly, by name. It is defined by CSS Cascade 5, and the syntax is summarized in MDN's @layer documentation.

How it works — the sort order

The criteria set by section 6.1 of the specification are checked in this order. If it is decided at an earlier one, the later ones aren't looked at.

1. 출처와 중요도   (사용자 important > 작성자 important > … )
2. 캡슐화 맥락     (섀도 트리)
3. 요소에 직접 붙은 스타일 (style 속성)
4. 레이어
5. 특정성
6. 나온 순서

!important is decided at number 1, so it is still the strongest. Next comes the style attribute, then layers, and only after that specificity.

It takes just two lines

The rules at the layer stage are two lines.

Combining these two gives a result that looks backwards at first.

@layer reset, components;

@layer components {
  #app .card .title { color: navy; }   /* 특정성 1,2,0 */
}
.title { color: crimson; }             /* 특정성 0,1,0, 레이어 밖 */

The winner is the crimson of .title. This is because what is outside a layer is the last layer, and for normal declarations the later layer wins. Specificity never gets its turn.

Conversely, if you add !important, the order flips. The !important in the reset layer beats the !important in the components layer, and an !important outside a layer loses to the !important of every layer. The specification explains that this is "the same logic by which important reverses the priority of normal declarations."

Order is fixed where a name first appears

Layer order is the order in which the names first appear. So if files are split up and a bundler reorders them, the layer order changes too.

That's why in practice you put one blockless declaration statement at the very top.

@layer reset, base, components, utilities;

This one line becomes the contract for the whole file. From then on, whichever file is read first, the order is fixed.

It pairs with :where()

:where() always has a specificity of 0. :where(.card .title) is 0,0,0, so it even loses to a single tag selector.

If layers decide "which group wins," :where() makes defaults that are easy to override within the same group. If a library uses both together when providing default styles, the people using it no longer need to write long selectors.

:is() and :not() are different — they follow the highest one inside the parentheses. :is(.card, #app) is 1,0,0.

What it looks like in the field

Here are three things you actually run into when introducing layers.

First, you can't put existing code into layers all at once. In that case all the remaining code becomes "outside a layer" and is stronger than every layer. This is not an accident but rather a safe migration path — newly created layers don't suddenly overwrite the existing screens.

Second, you can attach a layer to @import. If you push someone else's CSS wholesale into one layer with @import url(reset.css) layer(reset);, that entire library becomes weaker than your code. The !important you used to beat the library disappears wholesale.

Third, don't declare a layer for the first time inside a media query. The specification says that the layer enters the order only when the condition is true. A stylesheet whose layer order varies with screen width produces bugs that can't be reproduced. The single declaration line at the top prevents this too.

What you will do in the next lab

After declaring four layers and filling in the rules, you check with the calculator which declaration wins. You first write down in the prediction file why outside a layer is the strongest and why adding !important flips it, and then check against the calculator.