CSSカスケード・詳細度・継承の解決アルゴリズム
なぜそのスタイルが勝つかを毎回ブラウザと同じ手順で判定でき、!important合戦やレイヤ事故を当て推量せず解消できる。オリジン・レイヤ・詳細度・出現順の比較順を原理から解き明かします。
- 1.ある宣言が勝つかは、オリジンと重要度→cascade layer→詳細度→出現順の優先順に上位から比較し、最初に差がついた段で決着する。詳細度は同段内の最後のタイブレークでしかない。
- 2.!important はオリジンの優先順位を反転させ、レイヤ間でも後勝ちでなく先勝ちになる。author の通常宣言はレイヤなし>後段レイヤ>先段レイヤだが、!important では順序が逆転する。
- 3.カスケードで勝者を1つ選んだ後、未指定プロパティは継承プロパティなら親の計算値、非継承なら初期値で埋め、inherit/initial/unset/revert がこの段を明示制御する。
カスケードは「上位の段で差がついたら即決」のアルゴリズム
同じプロパティに複数の宣言が当たったとき、どれが勝つか。CSSのカスケード(cascade)は、複数の比較基準を厳密な優先順位の階段として持ち、上の段から順に比べて最初に差がついた段で決着させる辞書式(lexicographic)アルゴリズムです。詳細度(specificity)は強力な基準ですが、この階段の中ほどにある一段にすぎません。オリジンや !important が違えば、詳細度が 1,0,0,0 の宣言が 0,0,0,1 に負けることもあります。比較の順序は次のとおりです。
1. オリジンと重要度(origin & importance)
2. cascade layer(!important では順序が反転)
3. (ここまで同じなら)詳細度(specificity)
4. 出現順(order of appearance、後に書いた方が勝つ)
上から順に比べ、ある段で優劣がついた瞬間に下の段は見ません。たとえばオリジンが違えば詳細度も出現順も無関係に決まります。「詳細度を上げても効かない」という相談の多くは、実はより上の段(レイヤや !important)で既に負けているケースです。
カスケードはあくまで「そのプロパティに一致した宣言の集合」の中で勝者を選ぶ手続きです。セレクタが要素にマッチするか否かの判定(matching)はカスケードの前段で、ここで集めた宣言群を上記の階段にかけます。マッチしていない宣言はそもそも候補に入りません。
オリジンと重要度:6段の優先順位
最上段の比較基準はオリジン(宣言の出どころ)と重要度(!important の有無)の組です。オリジンには user agent(ブラウザ既定)、user(ユーザー設定)、author(サイト作者)の3種があり、!important の有無と掛け合わせて6つの優先順位が定義されます。重要な点は、!important が付くと順序が反転することです。
| 優先順位 | オリジン×重要度 | ねらい |
|---|---|---|
| 最強 | user agent !important | ブラウザが死守する規定(ほぼ未使用) |
| ↑ | user !important | ユーザーのアクセシビリティ要求を作者より優先 |
| ↑ | author !important | 作者が通常宣言より強く効かせたい指定 |
| ↓ | author 通常宣言 | 作者の大半のスタイル |
| ↓ | user 通常宣言 | ユーザー設定の通常宣言 |
| 最弱 | user agent 通常宣言 | ブラウザ既定値 |
通常宣言では author > user > user agent ですが、!important 付きでは user agent > user > author と逆転します。これは設計意図そのもので、ユーザーが視認性のために !important で上書きした指定を、作者の !important で握りつぶせないようにするためです。アクセシビリティの最後の砦がこの反転です。アニメーション中の宣言やトランジション中の値も、この階段の特定の位置に差し込まれます。トランジション中の値はすべての !important よりさらに上(最上段)に置かれ、アニメーション中の宣言は author !important の直下・author 通常宣言の直上に入ります。
cascade layer:詳細度より上で順序を支配する
CSS Cascade Level 5 で導入された **cascade layers(@layer)**は、オリジン×重要度が同じ宣言群を、詳細度より上の段で順序付けする仕組みです。これが効くのは「同じ author の通常宣言どうし」のような、従来なら詳細度勝負になっていた領域です。
@layer reset, framework, app; /* 宣言順でレイヤの優先度が決まる */
@layer framework {
.btn { color: blue; } /* 詳細度 0,1,0 */
}
@layer app {
a { color: green; } /* 詳細度 0,0,1(より弱い)*/
}
author の通常宣言における優先度は、レイヤなし(unlayered)> 後で宣言したレイヤ > 先に宣言したレイヤの順です。上の例では app が framework より後なので、app レイヤの a { color: green } が、詳細度では上回る framework レイヤの .btn { color: blue } に詳細度を見るまでもなく勝ちます。詳細度はあくまで「同じレイヤ内」での最後のタイブレークに格下げされます。これがレイヤの核心で、低詳細度のリセットやライブラリを先頭レイヤに置けば、アプリ側は詳細度を盛らずに上書きできます。
通常宣言ではレイヤなしが最強・後段レイヤが優先ですが、!important 付きでは完全に逆転します。!important どうしの優先度は、先に宣言したレイヤ > 後段レイヤ、そしてレイヤなしの !important が最弱になります。つまりリセット用に先頭へ置いたレイヤに !important を仕込むと、それが author の中で最強になります。「レイヤを入れたら !important の効きが逆になった」という混乱の正体はこれです。
詳細度:a,b,c の三つ組を辞書式に比較
オリジン・重要度・レイヤがすべて同じとき、初めて**詳細度(specificity)**が比較されます。詳細度はセレクタを (a, b, c) の3成分に数えた三つ組で、辞書式に大小を判定します。
| 成分 | 数えるもの | 例 |
|---|---|---|
| a(最上位) | IDセレクタの個数 | #main → (1,0,0) |
| b(中位) | クラス・属性・擬似クラスの個数 | .btn:hover → (0,2,0) |
| c(最下位) | 要素型・擬似要素の個数 | a::before → (0,0,2) |
比較は a を先に見て、同じなら b、さらに c、という辞書式です。b がどれだけ多くても a が1つ多い相手には勝てません(俗に「255進ではなく無限桁の辞書式」と言われる所以)。インラインスタイル(style 属性)はセレクタを持たないため詳細度の枠外で、author 宣言の中では実質最強の出現順位置に置かれます。!important は前述のとおり詳細度ではなく上位の重要度段で効くもので、「詳細度を無限大にする」わけではない点に注意してください。
#nav .item a → a,b,c = 1,1,1
.menu .item.active → a,b,c = 0,3,0
→ a で 1 対 0、最初の桁で #nav 側が勝つ(b,c は見ない)
擬似クラスにも例外があります。:is() :has() :not() は引数内で最も詳細度の高いセレクタの値を採用し、:where() は引数が何であろうと詳細度を 0,0,0 に潰します。:where() は「マッチはさせたいが詳細度は上げたくない」リセットやデフォルト指定に有効です。
ライブラリやデザインシステムで :where(.card) { padding: 1rem } と書くと、詳細度 0,0,0 のためアプリ側は要素セレクタ1つ(c=1)でも上書きできます。:is(.card) だと 0,1,0 になり上書きの敷居が上がります。低詳細度を意図的に設計する道具が :where() で、cascade layer と組み合わせると上書き容易性をさらに制御できます。
出現順:最後のタイブレーク
ここまでの段がすべて並んだ場合の**最終タイブレークが出現順(order of appearance)**です。原則は単純で、後に現れた宣言が勝つ。同一ファイル内なら下に書いた方、複数スタイルシートなら後で読み込まれた方が後勝ちです。@import で取り込んだ規則は、取り込み元のその位置に展開された順で並びます。よくある「同じセレクタを2回書いたら後者が効く」のは、オリジン・レイヤ・詳細度がすべて同点で、この出現順だけで決着している状態です。
同点(オリジン/レイヤ/詳細度がすべて等しい)なら:
後に書いた宣言が勝つ
.btn { color: red; }
.btn { color: blue; } ← こちらが採用される
継承と初期値:勝者を選んだ後の値の計算順序
カスケードはプロパティごとに勝者の宣言を1つ選ぶところまでです。その後、要素の各プロパティの**計算値(computed value)を確定する段が続きます。ここでカスケードの勝者がなければ、継承プロパティ(color、font-*、visibility など)は親要素の計算値を受け継ぎ、非継承プロパティ(margin、border、background など)は各プロパティの初期値(initial value)**になります。この「カスケード→継承/初期化」の順序が値解決の骨格です。
| キーワード | 意味 | 効果 |
|---|---|---|
| inherit | 親の計算値を明示的に採用 | 非継承プロパティでも継承を強制 |
| initial | そのプロパティの初期値に戻す | 継承プロパティでも親を無視し規定へ |
| unset | 継承プロパティなら inherit、非継承なら initial | プロパティの素の性質に従う |
| revert | 現オリジンの宣言を捨て下位オリジンの結果へ | author 指定を user/UA 段まで巻き戻す |
unset は「そのプロパティが本来継承するかどうか」に従って inherit か initial のどちらかに化けるショートカットです。revert はさらに踏み込み、現在のオリジンの宣言だけを無かったことにして、一段下のオリジン(author を捨てて user/UA)の結果を採用します。all: revert でその要素を実質ブラウザ既定の見た目へ戻せます。revert-layer はレイヤ単位の同種で、現レイヤの宣言を捨てて下位レイヤの結果へ巻き戻します。これらは「カスケードのどの段までやり直すか」を宣言側から指定する制御弁です。
カスケード問では、(1) 比較順がオリジン×重要度→レイヤ→詳細度→出現順で、上位段で差がつけば下位は見ない辞書式である点、(2) !important がオリジン順とレイヤ順の双方を反転させ、user !important が author !important に勝つ点、(3) 詳細度 a,b,c の辞書式比較と :where()=0,0,0/:is()=引数最大、(4) インラインは詳細度枠外、(5) カスケード後に継承プロパティは親計算値・非継承は初期値で埋まる順序、(6) inherit/initial/unset/revert の差、が頻出です。「詳細度を上げれば必ず勝つ」は上位段を無視した誤りで定番の引っかけです。
まとめ
CSSのカスケードはオリジン×重要度→cascade layer→詳細度→出現順を上から比べ、最初に差がついた段で決着する辞書式アルゴリズムです。!important はオリジン順(通常 author>user>UA を UA>user>author へ)とレイヤ順の双方を反転させ、ユーザーのアクセシビリティ指定を守ります。cascade layers は詳細度より上の段で順序を支配し、author 通常宣言ではレイヤなし>後段レイヤの順、!important では逆転します。詳細度は a,b,c の三つ組の辞書式比較で、:where() は 0,0,0、:is()/:not()/:has() は引数の最大値を採り、インラインは枠外です。勝者を選んだ後は継承プロパティが親の計算値・非継承が初期値で埋まり、inherit/initial/unset/revert がこの段を明示制御します。土台のセレクタやボックスモデルは CSS、勝者確定後のサイズ解決は CSSレイアウトアルゴリズム、スタイル計算がどこで走るかは クリティカルレンダリングパス と レンダリングパイプライン詳説 で押さえると、宣言の勝敗から画面表示までが一本につながります。
Web/フロントエンド Article
CSSカスケード・詳細度・継承の解決アルゴリズムを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
CSS
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
!important はオリジンの優先順位を反転させ、レイヤ間でも後勝ちでなく先勝ちになる。author の通常宣言はレイヤなし>後段レイヤ>先段レイヤだが、!important では順序が逆転する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「CSS / カスケード」に近いか確認する。
- 強みである「ある宣言が勝つかは、オリジンと重要度→cascade layer→詳細度→出現順の優先順に上位から比較し、最初に差がついた段で決着する。詳細度は同段内の最後のタイブレークでしかない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。