CSS講座 / 継承・詳細度・カスケードで適用値を決める

カスケードは競合するCSS宣言から値を選ぶ仕組みで、詳細度はその比較条件の一つです。継承は必要な場合に親の値を受け取る別の仕組みであり、親に強いセレクターがあるから子への直接指定に勝つ、という意味ではありません。

継承と直接指定を分ける

文字色やフォント関連は通常継承しますが、borderやpaddingは初期状態で継承しません。次のHTMLとCSSだけを適用する場合を考えます。

#main {
  color: purple;
  border: 1px solid purple;
}
p { color: #555; }
<main id="main">
  <h1>Small Garden</h1>
  <p>Visitor information.</p>
</main>

h1には直接の色指定がないためmainから紫を継承します。pには直接指定した灰色が使われます。親の#mainがIDでも、子のpの直接指定と同じ要素上で競合する比較ではありません。枠はmainのボックスに付き、各子の枠へ複製されません。

通常継承しないプロパティでも、border: inheritのように親の計算値を明示的に受け取れます。継承するプロパティでも、その要素に適用する宣言があれば先に処理します。MDNの継承の説明でプロパティごとの違いを確認できます。

カスケードの判断順序を追う

  1. セレクターが一致し、構文が有効で、メディアクエリーなどの条件を満たす宣言だけを候補にします。
  2. 出所、重要度、レイヤーなど、詳細度より先に比較する条件を調べます。ブラウザー・利用者・作成者のスタイル、important、layerなどが関係します。
  3. 適切な同じ条件の中で詳細度を比較します。
  4. この実習のようにscopeなどの追加条件がない場合、同点なら後に書かれた宣言が勝ちます。

判定はプロパティごとに行い、必要な値が決まらない場合に継承や初期値を使います。一つの規則が勝つと前のブロック全体が消えるわけではありません。アニメーション、トランジション、レイヤー、scopeには追加の順序があるため、この基本例だけですべての競合を説明できません。

W3Cのカスケードの並べ替え規則が出所・重要度・レイヤーの関係を定義しています。ここではそれらを増やさず比較し、レイヤーを使う構成はスタイルシートの分割・入れ子・レイヤーで扱います。

詳細度を三列で比較する

セレクター ID クラス・属性・擬似クラス 要素・擬似要素
p 0 0 1
.notice 0 1 0
p.notice 0 1 1
[required] 0 1 0
a:hover 0 1 1
p::before 0 0 2
#opening 1 0 0
#main .notice 1 1 0

左の列から比べ、差が付いた列で決めます。IDを100点、クラスを10点のように足す十進法の点数ではありません。クラスを多数並べてもIDの列を繰り上げることはできません。結合子や空白、全称セレクター自体はこの数を増やしません。

p.notice { color: #087f5b; }
.notice { color: tomato; }

出所・重要度・レイヤーが同じなら、p.noticeは.noticeより詳細度が高く、先に書いても緑が勝ちます。同じセレクター同士なら記述順を比べます。一部の関数形擬似クラスには追加規則があるため、MDNの詳細度リファレンスも確認してください。

規則全体ではなく各プロパティを比べる

.notice {
  color: blue;
  padding: 12px;
  border: 1px solid #087f5b;
}
.notice {
  color: tomato;
  padding: 20px;
}

同じ要素にこの二つが適用される場合、colorとpaddingは後の値、borderは前の値が残ります。後の規則にborderがないことは、前のborderを消す指定ではありません。

文字色はID規則、余白は後のクラス規則、枠は前のクラス規則から決まることもあります。開発者ツールの打ち消し線も一宣言ずつ読みます。colorに線があるだけで同じブロックのpaddingも無効とは判断できません。

完全な例の結果を予想する

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Small Garden</title>
  <style>
body { margin: 24px; font-family: sans-serif; line-height: 1.7; }
    #main { color: purple; }
    p { color: #555; }
    .notice {
      color: blue;
      padding: 12px;
      border: 1px solid #087f5b;
    }
    p.notice { color: #087f5b; }
    .notice { color: tomato; padding: 20px; }
    #opening { color: #1d4ed8; }
  </style>
</head>
<body>
<main id="main">
    <h1>Small Garden</h1>
    <p id="opening" class="notice">A: We open at 9am every day.</p>
    <p class="notice">B: Prepare a water bottle and comfortable shoes.</p>
    <p>C: Please inquire at the information desk.</p>
  </main>
</body>
</html>

全体をindex.htmlに保存する前に、A・B・Cの色と案内の余白を予想します。実行するとh1はmainから継承した紫、AはIDの青#1d4ed8、Bはp.noticeの緑#087f5b、Cはpの直接指定の灰色#555になります。

A・Bのpaddingは、同じ詳細度の.noticeのうち後の20pxが使われます。後にborderがないため、前の1pxの緑の実線は残ります。ComputedではA・B・Cがそれぞれrgb(29, 78, 216)、rgb(8, 127, 91)、rgb(85, 85, 85)と表示される場合があり、同じ色を表します。余白と枠も別々に確認します。

importantとインライン指定の条件

!importantは宣言の重要度を変えます。詳細度の三列へ巨大な点数を足すものではありません。例の最後に次を加えると、作成者の通常宣言よりimportant宣言が優先し、A・Bはtomatoになります。通常のIDの色指定でもこれには勝ちません。

.notice { color: tomato !important; }

ただしimportantが常にすべてのスタイルより強いわけではありません。利用者のimportant、トランジション中の値、レイヤー順などの条件も関係します。結果を確認したら追加した規則を削除します。

<p class="notice" style="color: purple;">B: Information on what to prepare</p>

importantを外した元の例でBへこのインライン指定を入れると、通常のスタイルシート宣言より優先して紫になります。インラインは上の基本三列とは別に扱います。通常のインライン色より作成者importantが勝つ場合もあるので、「インラインが無条件に最強」とも言えません。

競合を一条件ずつ診断する

  1. p.noticeを最後に移動します。Bは緑、AはIDの青のままです。詳細度の差は順番だけでは逆転しません。
  2. そのセレクターを.noticeに変え、最後に置きます。今度は同じセレクターなのでBには最後の緑が使われます。確認後は元へ戻します。
  3. Aのid="opening"だけを外します。AもBと同じ緑になります。親mainのIDが残っていても、子への直接指定を継承値が追い越すわけではありません。IDを戻します。
  4. importantとインラインは別々の手順で試し、前の実習の規則を残さず元に戻します。

予想と違うときはStylesとComputedで要素を選び、該当するプロパティの出所・重要度・セレクター・順番を調べます。原因を見ずにIDやimportantを追加すると、その場で色が変わっても保守が難しくなります。