CSSの詳細度の数え方と、指定がぶつかったときに勝つ順番

​​CSS​JavaScript,Playwright,詳細度

CSS を書いたのに効かない、ほかの指定を上書きできない、というときは、同じ要素に当たった指定どうしの「強さ」を比べると原因が分かります。強さの順は、弱いほうから要素、クラス、ID、インラインの style 属性、!important です。以下では、セレクターの詳細度の数え方と、それぞれがぶつかったときにどちらが勝つかを、ブラウザーで実際に表示して確かめます。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、ヘッドレスの Chromium 153 です。検証用のページを開き、getComputedStyle で文字色を取り出しました。詳細度は、Chrome DevTools Protocol(DevTools がブラウザーとやりとりする仕組み)の CSS.getMatchedStylesForNode が返す値を記録しました。Firefox と Safari、@layer(カスケードレイヤー)を使った場合は試していません。

勝つ順番は、要素 < クラス < ID < インライン < !important

まず、2つの指定を1組ずつぶつけた結果です。どの組も、文字の色だけを指定しています。1組目と2組目は、弱いほうを後に書いています。

5組の指定と、その結果の文字色。要素とクラスはクラスの水色、クラスとIDはIDのピンク、IDとインラインスタイルはインラインの紫、インラインスタイルと要素セレクターの!importantはオレンジ、IDの!importantとインラインの!importantはインラインの緑になっている
指定を2つずつぶつけたときに、勝った指定の色で文字が表示されたところ

検証環境で取り出した文字色は、次のとおりでした。

ぶつけた指定 勝った指定 文字色
.c(クラス)と、後に書いた p(要素) クラス rgb(6, 122, 152)
#t(ID)と、後に書いた .c(クラス) ID rgb(216, 88, 120)
#t(ID)と、style="color: #7a4fc0" インライン rgb(122, 79, 192)
style="color: #7a4fc0" と、p { color: #c06a00 !important; } !important rgb(192, 106, 0)
#t { color: #c06a00 !important; } と、style="color: #2a8a3a !important" インラインの !important rgb(42, 138, 58)

4組目では、いちばん弱い要素セレクター p でも、!important が付いていればインラインの style に勝ちました。!important は詳細度を上げるものではなく、詳細度より先に比べられる、別の段階の決まりです。MDN の !important のページにも、重要な宣言は同じオリジン(作成者のスタイルシートなど、スタイルの出どころ)とカスケードレイヤーにあるほかの宣言をすべて上書きする、と書かれています。

詳細度の数え方と、3つの列の比べ方

セレクターどうしの強さは、詳細度で比べます。詳細度は、セレクターに含まれる ID の数、クラスの数、要素の数を 1-0-0 のように3つ並べたものです。MDN の詳細度の解説によると、属性セレクター([type="radio"] など)と疑似クラス(:hover など)はクラスの列に、疑似要素(::before など)は要素の列に数えます。全称セレクター * と、> や空白などの結合子は数えません。

同じ段落に当たるセレクターを並べ、Chromium が返した詳細度を記録しました。

html
<div id="box"><p id="lead" class="note a b c d e f g h i j k">テキスト</p></div>
セレクター 詳細度
* 0-0-0
p 0-0-1
div p 0-0-2
.note 0-1-0
[id="lead"] 0-1-0
p.note 0-1-1
p:first-child 0-1-1
div > p.note 0-1-2
.a.b.c.d.e.f.g.h.i.j.k 0-11-0
#lead 1-0-0
#box .note 1-1-0
#box #lead 2-0-0

[id="lead"] は ID の要素を選んでいますが、属性セレクターなので 0-1-0 です。#lead と書いたときだけ、ID の列に数えられます。

比べるときは、左の ID の列から順に見ます。ID の数が多いほうが勝ち、同じなら次のクラスの列、それも同じなら要素の列で比べます(Selectors Level 4 の詳細度の節)。

クラスを11個並べても、ID 1つには勝てない

詳細度は、10個で次の桁に繰り上がる数ではありません。#t(1-0-0)を先に書き、クラスを11個つなげたセレクター(0-11-0)を後に書いたところ、文字は ID で指定したピンク rgb(216, 88, 120) のままでした。

css
#t { color: #d85878; }
.a.b.c.d.e.f.g.h.i.j.k { color: #067a98; }

クラスを256個つなげても、結果は同じピンクでした。このとき Chromium が返した詳細度は 0-256-0 ではなく 0-255-0 で、上限で止まっていました。Selectors Level 4 の同じ節では、実装が詳細度の数に上限を設ける場合、上限を超えた値は繰り上がらずに上限で止める、と定めています。どちらにしても、クラスの数で ID を追い越すことはできません。

詳細度が同じなら、後に書いたほうが勝つ

詳細度が同じセレクターどうしは、CSS の中で後に書いたほうが勝ちます。class="b a" の段落に、.a と .b の2つのルールを当てて確かめました。

css
.a { color: #067a98; }
.b { color: #d85878; }

この順ではピンク rgb(216, 88, 120)、.b を先に書き .a を後に書くと水色 rgb(6, 122, 152) になりました。HTML の class 属性の中の順番(b a)は関係ありません。class="a b" の段落で、2つのルールを別々の <style> 要素に分けた場合も、後に読み込んだほうの .b が勝ちました。

ID を属性セレクターで書いた [id="t"] と .a も、どちらも 0-1-0 なので、後に書いた .a の水色になりました。

!important どうしは、インラインが勝ち、次に詳細度で決まる

!important が付いた宣言どうしがぶつかったときは、もう一度詳細度で比べます。

css
#t { color: #d85878 !important; }
.a { color: #067a98 !important; }

この順で書くと、後に書いた .a ではなく、詳細度が高い #t のピンク rgb(216, 88, 120) になりました。上書きのために !important を足すと、その指定を後から変えたいときにも、!important と、同じ以上の詳細度(同じなら後に書く)が必要になります。MDN の詳細度の解説でも、!important を使って詳細度を上書きすることは避けるようにと書かれています。

インラインの style に !important が付いていると、ページの CSS(作成者のスタイルシート)のどの !important よりも強くなります。最初の画像の5組目では、ID に !important を付けた指定より、style="color: #2a8a3a !important" の緑が勝ちました。MDN の !important のページにも、インラインの重要なスタイルは、レイヤーに関係なく、ほかのすべての作成者の重要なスタイルより優先されるとあります。

親に付けた ID の指定は、子の要素セレクターに負ける

詳細度で比べるのは、その要素に直接当たったセレクターどうしだけです。親から受け継いだ(継承した)値は、詳細度がいくら高くても、要素に直接当たった指定に負けます。

css
#parent { color: #d85878; }
h1 { color: #067a98; }
html
<div id="parent">
  <h1>見出し</h1>
  <p>段落</p>
</div>

検証環境では、見出しは h1 の水色 rgb(6, 122, 152) になりました。h1 の指定がない段落は、#parent から継承したピンク rgb(216, 88, 120) です。#parent の宣言に !important を付けても、見出しは水色のままでした。子に受け継がれるのは値だけで、!important の強さは受け継がれません。

この動きは、MDN の詳細度の解説の「直接対象の要素と継承されたスタイル」にも例があります。親に強いセレクターで色を指定したのに、中の見出しやリンクの色が変わらないときは、その要素に直接当たっている指定がないかを確かめます。

JavaScript の el.style で付けた値は、インラインスタイルとして比べられる

JavaScript で el.style.color = '#7a4fc0' のように値を入れると、要素の style 属性に書き込まれます。検証環境では、style 属性が color: rgb(122, 79, 192); になり、#t の指定に勝って紫になりました。

ただし、インラインスタイルなので、スタイルシートの !important には負けます。

css
#t { color: #d85878; }
.a { color: #067a98 !important; }

el.style.color で紫にしたあと、この .a が当たるように class を足すと、文字は .a の水色 rgb(6, 122, 152) に変わりました。JavaScript からページの CSS の !important より強くしたいときは、setProperty() の3つ目の引数に 'important' を渡します。

js
el.style.setProperty('color', '#7a4fc0', 'important');

実行すると、style 属性は color: rgb(122, 79, 192) !important; になり、文字は紫に戻りました。インラインの !important なので、ページの CSS の !important より強くなります。ただし、インラインの !important は、ページの CSS のほかの宣言からは上書きできなくなります。まずは、JavaScript で付け外しするクラスの詳細度を、上書きしたい指定と同じ以上にできないかを考えます。

​Posted by 86