TT Lab
はじめる
学ぶ 学習パス コース

CSS — 後に書いた規則が勝つ

レイヤーを使い始めると判定が一段増える

TT Labで続きを見る

一言でいうと

カスケードの並べ替えの順序では、レイヤーが詳細度よりも先に決まります。そのため、レイヤーを使い始めたシートでは、詳細度の低いルールが高いルールに勝つことが、正常に起こります。

なぜ必要なのか

CSSが崩れていく道筋は、ほとんどいつも1つです。他人のルールに勝つためにセレクターを1つ長く書き、次の人はもっと長く書き、結局!importantが出てきます。すると、そのプロパティは永遠に!importantでしか扱えなくなります。

問題の根は、「勝つべき順序」と「詳細度」が、もともと別のものだという点にあります。リセットのルールは常に最も弱く、ユーティリティクラスは常に最も強くなければなりません。ところがCSSには、それを直接言う方法がなく、これまでは、セレクターの長さという間接的な手段で真似してきました。

@layerは、その順序を名前で直接書く構文です。CSS Cascade 5が定義し、MDNの@layerのドキュメントが構文を整理しています。

どう動くのか: 並べ替えの順序

仕様の6.1節が定める基準は、この順序で見ます。前で決まれば、後ろは見ません。

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

!importantは1で決まるため、依然として最も強いです。その次がstyle属性で、その次がレイヤー、そのあとでようやく詳細度です。

2行で終わります

レイヤーの段階のルールは、2行です。

この2つを合わせると、最初は逆に見える結果になります。

@layer reset, components;

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

勝つのは、.titleのcrimsonです。レイヤーの外は最後のレイヤーであり、通常の宣言ではあとのレイヤーが勝つからです。詳細度は、見られる番が来ません。

逆に、!importantを付けると、順序がひっくり返ります。resetレイヤーの!importantがcomponentsレイヤーの!importantに勝ち、レイヤー外の!importantは、すべてのレイヤーの!importantに負けます。仕様は、これを「importantが通常の宣言の優先順位を逆転させるのと同じ論理」だと説明しています。

順序は最初に出てきた場所で決まります

レイヤーの順序は、名前が最初に出てきた順です。そのため、ファイルが複数に分かれ、バンドラーが順序を入れ替えると、レイヤーの順序も変わります。

そのため、実務では、いちばん上にブロックのない宣言型のルールを1行置きます。

@layer reset, base, components, utilities;

この1行が、ファイル全体の契約になります。それ以降は、どのファイルが先に読み込まれても、順序は固定です。

:where()と対になります

:where()は、詳細度が常に0です。:where(.card .title)は0,0,0なので、タグセレクター1つにも負けます。

レイヤーが「どの集団が勝つか」を決めるなら、:where()は、同じ集団の中で上書きしやすい基本値を作ります。ライブラリが基本スタイルを与えるときに、この2つを一緒に使えば、使う側はセレクターを長く書く必要がなくなります。

:is()と:not()は違います。かっこの中で最も高いものに従います。:is(.card, #app)は1,0,0です。

現場での姿

レイヤーを導入するときに実際にぶつかるものを、3つ書き留めておきます。

1つ目は、既存のコードを一度にレイヤーに入れられないことです。そのとき、残ったコードはすべて「レイヤーの外」になり、すべてのレイヤーよりも強くなります。これは事故ではなく、むしろ安全な移行の道筋です。新しく作ったレイヤーが、既存の画面を突然上書きすることがありません。

2つ目は、@importにレイヤーを付けられることです。@import url(reset.css) layer(reset);で他人のCSSをまるごと1つのレイヤーに押し込むと、そのライブラリ全体が自分のコードよりも弱くなります。ライブラリに勝つために使っていた!importantが、丸ごとなくなります。

3つ目は、メディアクエリの中でレイヤーを最初に宣言しないことです。仕様は、条件が真のときだけ、そのレイヤーが順序に入ると述べています。画面幅によってレイヤーの順序が変わるシートは、再現できないバグを生みます。いちばん上の宣言型のルール1行が、これも防いでくれます。

次のラボですること

4つのレイヤーを宣言してルールを埋めたあと、どの宣言が勝つかを計算ツールで自分で確認します。レイヤーの外がなぜ最も強いのか、!importantを付けるとなぜひっくり返るのかを、先に予測ファイルに書いてから、計算ツールで答え合わせをします。