詳細度

詳細度 (Specificity) は、ある要素に最も関連性の高い CSS 宣言を決定するためにブラウザーが使用するアルゴリズムで、これによって、その要素に使用するプロパティ値が決定されます。詳細度のアルゴリズムは、CSS セレクターの重みを計算し、競合する CSS 宣言の中からどのルールを要素に適用するかを決定します。

メモ: ブラウザーは、カスケードのオリジンと重要度を決定したに、詳細度を検討します。言い換えれば、プロパティ宣言が競合している場合、そのプロパティの優先順位を保有する 1 つのカスケードのオリジンとレイヤーのセレクター間でのみ、詳細度が関連し比較されます。優先順位を持つカスケードレイヤーで競合する宣言のセレクターの詳細度が等しいとき、スコープの詳細度と出現順序が関連するようになります。

詳細度の計算方法

詳細度は、与えられた CSS 宣言に適用される重みを計算するアルゴリズムです。重みは、その要素(または擬似要素)に一致するセレクターの中で、それぞれの重み分類別のセレクターの数によって決定されます。同じ要素に異なるプロパティ値を提供する宣言が 2 つ以上ある場合、一致するセレクターを持つスタイルブロックの宣言値のうち、アルゴリズムによる重みが最も大きいものが適用されます。

詳細度アルゴリズムは、基本的に 3 種類のセレクターに対応する ID、CLASS、TYPE という 3 つの分類または重みの 3 列の値で構成されます。この値は、それぞれの重み分類に含まれるセレクターの成分の数を表し、ID - CLASS - TYPE のように書かれます。 3 つの列は、要素に一致するセレクターの中で、各セレクターの重み分類のセレクターの成分の数を数えることによって作成されます。

セレクターの重み分類

ここでは、セレクターの重み分類を、詳細度の高いものから順番に並べています。

ID 列

ID セレクター、例えば #example のみを含みます。一致するセレクターに含まれる ID ごとに、重みの値に 1-0-0 を追加します。

CLASS 列

クラスセレクター.myClass など)や、属性セレクター([type="radio"][lang|="fr"] など)、擬似クラスセレクター(:hover, :nth-of-type(3n), :required など)を指します。一致するセレクターのクラス、属性セレクター、擬似クラスごとに、重みの値に 0-1-0 を追加します。

TYPE 列

要素型セレクターp, h1, td など)、擬似要素セレクター(::before, ::placeholder, それ以外のコロン 2 つの表記のセレクターすべて)を指します。一致するセレクター内での型や擬似要素ごとに、重みの値に 0-0-1 を追加します。

値なし

全称セレクター (*) および擬似クラス :where() とその引数は、重みの計算にはカウントされませんが、要素には一致します。全称セレクターと擬似クラスの値は 0-0-0 です。これらのセレクターは詳細度の計算に影響を与えません。

結合子(+, >, ~, " ", ||)は、何を選択するのかをより具体化することができますが、詳細度の重みに値を追加することはありません。

& 結合子は詳細度の重みを追加しませんが、入れ子になったルールは詳細度を追加します。詳細度および機能性の観点では、入れ子は:is() 擬似クラスとよく似ています。

入れ子と同様に、擬似クラスである :is():has()、否定 (:not()) 自体は重みを持ちません。しかし、これらのセレクターの引数は重みを持ちます。各セレクターの重みは、最も高い詳細度を持つセレクターのリストに掲載されているセレクターの引数から取得されます。同様に、入れ子になったセレクターの場合、入れ子になったセレクター部分が加える詳細度は、最も高い詳細度を持つ入れ子になったセレクターのカンマ区切りのリストに掲載されているセレクターです。

:not(), :is() および :has() の例外 については、後ほど説明します。

一致するセレクター

詳細度の重みは、一致するセレクターによって決まります。カンマで区切られた 3 つのセレクターからなるこの CSS セレクターを例に考えてみましょう。

css
[type="password"],
input:focus,
:root #myApp input:required {
  color: blue;
}

上のセレクターリストにある [type="password"] セレクターは、詳細度の重みが 0-1-0 で、 color: blue の宣言をすべてのパスワードの入力型に適用します。

すべての入力は、型にかかわらず、フォーカスを受けると、リストの 2 つ目のセレクター input:focus に一致し、その詳細度は 0-1-1 です。この重みは、 :focus 擬似クラス (0-1-0) と input 型 (0-0-1) で構成されています。パスワード入力にフォーカスがある場合、 input:focus に一致し、スタイル宣言 color: blue の詳細度の重みは 0-1-1 となります。パスワードにフォーカスがない場合、詳細度の重みは 0-1-0 のままです。

id="myApp" という属性を持つ要素にネストされた必須入力の詳細度は、 1 つの ID、 2 つの擬似クラス、 1 つの要素型に基づいて、 1-2-1 となります。

パスワード入力型に required がついている場合、 id="myApp" が設定された要素の内部にある場合、フォーカスがあるかどうかに関わらず、詳細度の重みは ID ひとつ、擬似クラス 2 つ、要素型 1 つなので 1-2-1 になります。なぜこの場合、詳細度の重みが 0-1-10-1-0 ではなく、 1-2-1 になるのでしょうか?それは、詳細度の重みが最も大きい一致するセレクターから、詳細度の重みが決まるからです。重みは 3 つの列の値を左から右に比較することで決定されます。

css
[type="password"]             /* 0-1-0 */
input:focus                   /* 0-1-1 */
:root #myApp input:required   /* 1-2-1 */

3 つの列の比較

該当するセレクターの詳細度値が決まったら、それぞれの列のセレクター成分の数を左から右に並べて比較します。

css
#myElement {
  color: green; /* 1-0-0  - 勝ち!! */
}
.bodyClass .sectionClass .parentClass [id="myElement"] {
  color: yellow; /* 0-4-0 */
}

最初の列は ID 成分の値で、各セレクターのIDの数を表しています。競合するセレクターの ID 列の数値が比較されます。他の列の値がどうであれ、 ID 列の値がより大きいセレクターが勝ちます。上記の例では、黄色のセレクターが全体としてより多くの成分を保有しているとしても、重要なのは最初の列の値だけです。

競合するセレクターの ID 列の数字が同じであれば、次の CLASS 列を比較し、次のようになります。

css
#myElement {
  color: yellow; /* 1-0-0 */
}
#myApp [id="myElement"] {
  color: green; /* 1-1-0  - 勝ち!! */
}

CLASS 列は、セレクターに含まれるクラス名、属性セレクター、擬似クラスの数です。 ID 列の値が同じ場合、 TYPE 列の値にかかわらず、 CLASS 列の値が大きいセレクターが勝ちます。これは、以下の例で示されています。

css
:root input {
  color: green; /* 0-1-1 - CLASS 列が大きいため勝ち */
}
html body main input {
  color: yellow; /* 0-0-4 */
}

競合するセレクターの CLASS 列と ID 列の数字が同じであれば、 TYPE 列が関係してきます。 TYPE 列は、セレクターに入力された要素型と擬似要素の数です。最初の 2 列が同じ値である場合、 TYPE 列の数値が大きいセレクターが勝ちます。

競合するセレクターが 3 列すべてで同じ値を持つ場合、近接ルールが適用され、最後に宣言されたスタイルが優先されます。

css
input.myClass {
  color: yellow; /* 0-1-1 */
}
:root input {
  color: green; /* 0-1-1 後にあるので勝ち */
}

:is():not():has()、CSS 入れ子の例外

全一致の擬似クラス :is(), 否定擬似クラス :not() および関係擬似クラス :has() は、詳細度の計算では擬似クラスとは見なされません。それ自体は、詳細度算出のための重みを追加するものではありません。しかし、擬似クラスの括弧に渡されたセレクターの引数は、詳細度アルゴリズムの一部です。詳細度値の計算における一致するセレクターと否定の擬似クラスの重みは、引数の重みとなります。

css
p {
  /* 0-0-1 */
}
:is(p) {
  /* 0-0-1 */
}

h2:nth-last-of-type(n + 2) {
  /* 0-1-1 */
}
h2:has(~ h2) {
  /* 0-0-2 */
}

div.outer p {
  /* 0-1-2 */
}
div:not(.inner) p {
  /* 0-1-2 */
}

上記の CSS の組み合わせにおいて、:is(), :not() および :has() 擬似クラスが提供する詳細度の重みは、擬似クラスの値ではなく、セレクターの引数の値であることに注意してください。

これら3つの擬似クラスは、引数としてカンマで区切られた複雑なセレクターリストを受け入れます。この機能は、セレクターの詳細度を上げるために使用することができます。

css
:is(p, #fakeId) {
  /* 1-0-0 */
}
h1:has(+ h2, > #fakeId) {
  /* 1-0-1 */
}
p:not(#fakeId) {
  /* 1-0-1 */
}
div:not(.inner, #fakeId) p {
  /* 1-0-2 */
}

上記の CSS コードブロックでは、セレクターに #fakeId が含まれています。この #fakeId は、各段落の詳細度の重みに 1-0-0 を追加します。

CSS 入れ子を使用して複雑なセレクターリストを作成した場合、この動作は :is() 擬似クラスとまったく同じです。

css
p,
#fakeId {
  span {
    /* 1-0-1 */
  }
}

上記のコードブロックでは、複雑なセレクター p, #fakeId の詳細度は #fakeId から導かれると同時に span からも導かれるため、p span#fakeId span の両方に対して 1-0-1 という詳細度が作成されます。これは、:is(p, #fakeId) span セレクターと同等の詳細度です。

一般的に、詳細度は最小限に抑えたいものですが、特定の理由で要素の詳細度を上げる必要がある場合は、この 3 つの擬似クラスが役に立ちます。

css
a:not(#fakeId#fakeId#fakeID) {
  color: blue; /* 3-0-1 */
}

この例では、 3 つ以上の ID を持つリンク宣言、 a に一致する色の値が !important フラグを含む場合、またはリンクにインラインスタイルの色宣言がある場合で上書きされなければ、すべてのリンクは青色となります。このようなテクニックを使用する場合は、なぜそのハックが必要であったかを説明するコメントを追加してください。

インラインスタイル

要素に追加されたインラインスタイル(例えば、style="font-weight: bold;")は、作成者のスタイルシートにある通常のスタイルを常に上書きするので、最も高い詳細度を保有すると考えることができます。インラインスタイルは、1-0-0-0という詳細度の重みを保有すると考えてください。

インラインスタイルを上書きする唯一の方法は、!importantを使用することです。

多くの JavaScript フレームワークやライブラリーはインラインスタイルを追加します。インラインスタイルを使用する属性セレクターなど、とても的を絞ったセレクターで !important を使用することは、これらのインラインスタイルを上書きする一つの方法です。

html
<p style="color: purple">…</p>
css
p[style*="purple"] {
  color: rebeccapurple !important;
}

important フラグを使用する際は必ずコメントを入れてください。そうすれば、コードの管理者はなぜ CSS のアンチパターンが使用されたかを理解することができます。

!important の例外

important とマークされた CSS 宣言は、同じカスケード層とオリジン内の他の宣言を上書きします。技術的には、!importantは詳細度と何の関係もありませんが、詳細度やカスケードとは直接的に相互作用するものです。これは、スタイルシートのカスケードの順序を逆転させます。

同じオリジンとカスケードレイヤーからの宣言が競合し、一方のプロパティ値に !important フラグが設定されている場合、詳細度に関係なく、重要な宣言が適用されます。同じオリジンとカスケードレイヤーで !important フラグを持つ宣言が競合した場合、同じ要素に適用されると、より詳細度の高い宣言が適用されます。

!important を使用して詳細度を上書きすることは悪しき習慣と考えられており、そのための使用は避けるべきです。詳細度やカスケードについて理解し、効果的に使用することで、 !important フラグの必要性をなくすことができます。

!important を使用して外部の CSS (Bootstrap や normalize.css など)を上書きする代わりに、サードパーティのスクリプトを直接カスケードレイヤーにインポートするようにしてください。 CSS でどうしても !important を使用したい場合は、用途をコメントで記述しておくと、将来のコード保守者がその宣言がなぜ important とマークされたのかを知り、それを上書きしないようにすることができます。しかし、他の開発者が制御できない状態で組み込む必要があるプラグインやフレームワークを書くときには、絶対に !important を使用しないでください。

:where() の例外

詳細度を調整する擬似クラス :where() は、常にその詳細度が 0、0-0-0 に置き換えられています。これにより、どの要素を対象としても詳細度を上げることがない、とても特殊な CSS セレクターを作ることができるようになります。

CSS を編集するアクセス権を持たない開発者が使用するサードパーティ CSS を保有する場合、可能な限り詳細度の低い CSS を作成することは良い習慣と考えられています。例えば、あなたのテーマが以下のような CSS を入れている場合は次のようにします。

css
:where(#defaultTheme) a {
  /* 0-0-1 */
  color: red;
}

そうすれば、ウィジェットを搭載する開発者は、要素型セレクターを使用するだけで、簡単にリンクの色を上書きすることができます。

css
footer a {
  /* 0-0-2 */
  color: blue;
}

@scope ブロックが詳細度に影響する方法

@scope ブロック内にルールセットを記述しても、セレクターが使用されるスコープのルートや制限に関わらず、そのセレクターの特定の詳細度には影響しません。例えば次のようになります。

css
@scope (.article-body) {
  /* img はの詳細度は期待通り 0-0-1 になる */
  img { ... }
}

しかし、もし明示的に :scope 擬似クラスをスコープ付きセレクターの前に追加する場合は、その詳細度を計算する際に考慮する必要があります。 :scope は、すべての通常の擬似クラスと同様に、 0-1-0 の詳細度を持ちます。

css
@scope (.article-body) {
  /* :scope img の詳細度 0-1-0 + 0-0-1 = 0-1-1 となる */
  :scope img { ... }
}

@scope ブロック内で & セレクターを使用する場合、 & はスコープルートセレクターを表します。これは、内部的には :is() セレクターで囲まれたセレクターに書き換えられます。例えば、次のような場合です。

css
@scope (figure, #primary) {
  & img { ... }
}

& img:is(figure, #primary) img と同等です。

:is() は最も詳細度の高い引数(この場合は #primary)の詳細度を取るので、スコープされた & img セレクターは 1-0-0 + 0-0-1 = 1-0-1 となります。

詳細度に関する頭痛の種を処理するためのヒント

!important を使用する代わりに、カスケードレイヤーを使用し、 CSS 全体を通して、より詳細なルールで簡単にスタイルを上書きできるように、重み付けを低くすることを検討してください。 セマンティック HTML を使用すると、スタイルを適用するためのアンカーを提供するのに役立ちます。

詳細度を上げることなくセレクターを具体的にする

選択する要素の前にスタイル付けする文書の部分を示すことで、ルールはより詳細度が増します。追加する方法によって、以下のように、ある程度、かなり詳細度を上げたり、まったく詳細度を上げなかったりすることができます。

html
<main id="myContent">
  <h1>Text</h1>
</main>
css
#myContent h1 {
  color: green; /* 1-0-1 */
}
[id="myContent"] h1 {
  color: yellow; /* 0-1-1 */
}
:where(#myContent) h1 {
  color: blue; /* 0-0-1 */
}

どのような順番であっても、見出しは緑色になります。そのルールが最も詳細度が高いからです。

ID セレクターの詳細度の縮小

詳細度は、セレクターの形式に基づいています。 Id セレクターではなく、要素の id を属性セレクターとして入れることで、過剰に詳細度を加算することなく、より具体的に要素を特定することができます。前の例では、セレクター [id="myContent"] は ID を選択しているにもかかわらず、セレクターの詳細度を決定する際には属性セレクターとして扱われます。

また、セレクターをより詳細にする必要があるが、詳細度を全く加算したくない場合には、 :where() 詳細度調整用擬似クラスの引数として id やセレクターの任意の部分を入れることができます。

セレクターの複製による詳細度の向上

詳細度を上げるための特別な方法として、 CLASS または ID 列の重みを重複させることができます。複合セレクターの中で、ID、クラス、擬似クラス、属性のセレクターを重複して保有すると、詳細度を高めて、制御できないとても詳細度の高いセレクターを上書きすることができます。

css
#myId#myId#myId span {
  /* 3-0-1 */
}
.myClass.myClass.myClass span {
  /* 0-3-1 */
}

使用するとしても控えめにしてください。セレクターの重複を使用する場合は、常に CSS にコメントを入れましょう。

:is():not() を使用することで、親要素に id を追加することができなくても、詳細度を上げることができます。

css
:not(#fakeID#fakeId#fakeID) span {
  /* 3-0-1 */
}
:is(#fakeID#fakeId#fakeID, span) {
  /* 3-0-0 */
}

サードパーティの CSS より優先

カスケードレイヤーを活用することは、あるスタイルのセットが別のスタイルのセットよりも優先されるようにする標準的な方法です。カスケードレイヤーは、詳細度を使用せずにこれを可能にします。 レイヤーにインポートされた通常の(重要でない)作成者スタイルは、レイヤー外の作成者スタイルよりも低い優先順位を持ちます。

スタイルが編集できないか理解できないスタイルシートから来ていて、スタイルを上書きする必要がある場合、戦略としては、制御しないスタイルをカスケードレイヤーにインポートすることです。続いて宣言されたレイヤーのスタイルは優先され、レイヤーされていないスタイルは同じ起源の全てのレイヤーのスタイルより優先されます。

異なるレイヤーからの 2 つのセレクターが同じ要素に一致するとき、オリジンと重要性が優先されます。負けたスタイルシートのセレクターの詳細度は関係ありません。

html
<style>
  @import TW.css layer();
  p,
  p * {
    font-size: 1rem;
  }
</style>

上記の例では、ネストされたコンテンツを含むすべての段落のテキストは、 TW スタイルシートと一致するクラス名がいくつあっても、 1rem になります。

!important の防止と上書き

最良の方法は !important を使用しないことです。上記の詳細度に関する説明は、このフラグを使用しないようにしたり、遭遇したときに完全に削除したりするのに役立つはずです。

!important の必要性を感じさせないようにするには、以下のような方法があります。

  • 以前の !important 宣言のセレクターの詳細度を上げ、他の宣言よりも大きくする。
  • 同じ詳細度を与え、上書きする宣言の後に置く。
  • 上書きしようとするセレクターの詳細度を下げる。

このようなメソッドはすべて、前のセクションに則しています。

もし、作成者のスタイルシートから !important フラグを削除できない場合、重要なスタイルを上書きする唯一の解決策は、 !important を使用することです。 important 宣言を上書きするカスケードレイヤーを作成することは、優れた解決策です。これを行うには、次の 2 つの方法があります。

方法 #1

  1. 重要な宣言だけを含む別個の短いスタイルシートを作成し、削除できなかった重要な宣言は特に上書きする。
  2. このスタイルシートを、他のスタイルシートにリンクする前に、 layer() を使用した @import 文を使用して、CSS の最初のインポートとしてインポートしてください。これは、 important の上書きが最初のレイヤーとしてインポートされることを保証するためのものです。
html
<style>
  @import importantOverrides.css layer();
</style>

方法 #2

  1. スタイルシートの宣言の始めに、以下のように名前付きカスケードレイヤーを作成します。

    css
    @layer importantOverrides;
    
  2. 重要な宣言を上書きする必要がある場合は、その都度、指定されたレイヤーの中で宣言してください。重要なルールはレイヤーの中だけで宣言してください。

    css
    [id="myElement"] p {
      /* normal styles here */
    }
    @layer importantOverrides {
      [id="myElement"] p {
        /* important style here */
      }
    }
    

レイヤー内の重要なスタイルのセレクターの詳細度は、上書きしようとする要素に一致する限り、低くてもかまいません。通常のレイヤーは、レイヤーのスタイルはレイヤーなしのスタイルより優先順位が低いので、レイヤーの外側で宣言する必要があります。

文書ツリー内の近接性の無視

指定されたセレクターで参照される要素と他の要素との近接性は、詳細度には影響を与えません。

css
body h1 {
  color: green;
}

html h1 {
  color: purple;
}

これらの宣言は詳細度が同じですが、あとに宣言されたセレクターが優先されるため <h1> 要素は紫色になります。

直接対象の要素と継承されたスタイル

直接対象となる要素のスタイルは、継承されたルールの詳細度に関係なく、常に継承されたスタイルよりも優先されます。以下のような CSS と HTML がある場合...

css
#parent {
  color: green;
}

h1 {
  color: purple;
}
html
<html lang="ja">
  <body id="parent">
    <h1>これがタイトルです。</h1>
  </body>
</html>

h1 は紫色になりますが、これは h1 セレクターがその要素を詳細に対象としているためで、緑色は #parent 宣言から継承されたものです。

以下の CSS には、色を設定する <input> 要素を対象とする 3 つのセレクターがあります。与えられた入力に対して、 優先する色宣言の詳細度重み付けは、最も大きい重みで一致するセレクターになります。

css
#myElement input.myClass {
  color: red;
} /* 1-1-1 */
input[type="password"]:required {
  color: blue;
} /* 0-2-1 */
html body main input {
  color: green;
} /* 0-0-4 */

上記のセレクターがすべて同じ入力を対象とした場合、最初の宣言が ID 列に最も大きな値を保有しているため、入力は赤になります。

最後のセレクターは、 4 つの TYPE 成分を保有しています。整数値としては最も大きいですが、要素や擬似要素がいくつ入れられたとしても、たとえ 150 個あったとしても、 TYPE 成分が CLASS 成分より優先されることはありません。列の値は、列の値が等しい場合に左から右に向かって比較されます。

上の例のコードで id セレクターを属性セレクターに変換していた場合、最初の 2 つのセレクターは、以下のように同じ詳細度を持つことになります。

css
[id="myElement"] input.myClass {
  color: red;
} /* 0-2-1 */
input[type="password"]:required {
  color: blue;
} /* 0-2-1 */

複数の宣言が同じ詳細度である場合、 CSS の中で最後に見つかった宣言がその要素に適用されます。両方のセレクターが同じ <input> に一致する場合、色は青になります。

追加のメモ

詳細度について、いくつか覚えておきたいことがあります。

  1. 詳細度は、同じ要素が同じカスケードレイヤーやオリジンの複数の宣言によってターゲットにされている場合にのみ適用されます。特定度は、同じ重要度、同じオリジン、カスケードレイヤーの宣言にのみ関係します。一致するセレクターが異なるオリジンにある場合、カスケードはどちらの宣言が優先されるかを決定します。

  2. 2 つのセレクターが同じカスケードレイヤーとオリジンで同じ詳細度を持っている場合、スコープの近接度が計算されます。最もスコープの近接度が低いルールセットが優先されます。詳細と例えばについては、 @scope の競合の解決方法を参照してください。

  3. スコープの近接度がどちらのセレクターでも同じである場合、ソースの順序が重要になります。すべてがまったく同じである場合、最後のセレクターが優先されます。

  4. CSS のルールとして、要素が祖先から継承するルールよりも、直接対象となる要素 が常に優先されます。

  5. 文書ツリー内の要素の近接性は詳細度に影響しません。

仕様書

Specification
Selectors Level 4
# specificity-rules

関連情報