
CSS詳細度を完全理解する:なぜスタイルが適用されないのか
📷 Negative Space / PexelsCSS詳細度を完全理解する:なぜスタイルが適用されないのか
CSSの詳細度(A,B,C)システムをわかりやすく解説。!importantに頼らず、セレクターの優先順位を正確に把握する方法を実例とともに説明します。
フロントエンド開発者なら一度は経験するはずです。間違いなく正しく書いたはずのCSSルールが、ブラウザに無視されている。DevToolsを開くと、自分のルールに取り消し線が引かれていて、よくわからない別のセレクターが勝っている。
そして誰もがやってしまうこと——!importantを付ける。
これはその場しのぎにはなりますが、根本的な解決にはなりません。CSS詳細度の仕組みを理解すれば、!importantに頼らずにスタイルの競合を解決できるようになります。
(A, B, C) システムの仕組み
CSS詳細度は1つの数値ではなく、3つの独立した数値のセット「(A, B, C)」で表されます。比較は左の列から順に行われます。Aが大きければそのルールが優先。Aが同じならBを比較。Bも同じならCを比較します。
A列:IDセレクター
#header、#sidebarなどのIDセレクターがここに数えられます。1つで(1, 0, 0)になります。この列のせいで、現代のCSSではスタイリングにIDセレクターを使わないのが一般的です。一度使うと上書きが非常に難しくなります。
B列:クラス・属性・疑似クラス
クラスセレクター(.nav)、属性セレクター([type="text"])、疑似クラス(:hover、:focus、:nth-child())がここに入ります。それぞれBに1を加えます。
C列:要素型セレクター・疑似要素
div、p、ulなどの要素型セレクターと、::before、::afterなどの疑似要素がCに数えられます。
詳細度がゼロのもの
ユニバーサルセレクター*、コンビネーター(> + ~)、そして:where()は詳細度に影響しません。
具体例で確認する
/* (0, 0, 1) — 要素型セレクター1つ */
p { color: red; }
/* (0, 1, 0) — クラスセレクター1つ */
.intro { color: blue; }
/* (0, 1, 1) — クラス1つ + 要素型1つ */
.intro p { color: green; }
/* (1, 0, 0) — IDセレクター1つ */
#main { color: orange; }
/* (1, 1, 1) — ID1つ + クラス1つ + 要素型1つ */
#main .content p { color: purple; }
このすべてが同じ要素に適用される場合、(1, 1, 1)が最も高いので最後のルールが勝ちます。
よくある誤解
「セレクターが長いほど詳細度が高い」
直感的にそう感じますが、そうではありません。.nav .list .item a.linkは長いセレクターですが、詳細度は(0, 3, 2)です。一方、#headerは短くても(1, 0, 0)なので、前者に必ず勝ちます。
「後から書いたルールが常に優先される」
これは詳細度が完全に等しい場合にのみ成り立ちます。詳細度が異なる場合、ファイルの後ろに書いても低い方が負けます。
:is()、:not()、:where()の詳細度
:not()は引数の詳細度をそのまま引き継ぎます。:not(.hidden)は.hiddenのおかげで(0, 1, 0)となります。
:is()は引数の中で最も高い詳細度を取ります。:is(#main, .nav, p)は#mainの存在により(1, 0, 0)になります。リスト内の一部がID選択者であるだけで、全体の詳細度が跳ね上がるので注意が必要です。
:where()は常にゼロです。ベーススタイルを:where()で包んでおくと、後から簡単に上書きできます。現代のCSSライブラリがよく使うパターンです。
よくある実務シナリオ
UIライブラリを使っているときに頻繁に起こる問題です。
/* ライブラリのCSS */
.ui-button.ui-button--primary {
background-color: #0066cc;
}
/* 自分の上書きスタイル */
.my-button {
background-color: #ff5500;
}
ライブラリ:(0, 2, 0)、自分のルール:(0, 1, 0)。自分のスタイルが負けます。
解決方法:
- セレクターを繰り返す:
.my-button.my-buttonで(0, 2, 0)に合わせる - 親要素を追加する:
.my-app .my-buttonで(0, 2, 0)にする - !important:本当に最後の手段として
CSS詳細度計算ツールの使い方
CSS詳細度計算ツールを使えば、セレクターを貼り付けるだけで(A, B, C)の内訳がすぐわかります。
特に役立つ場面:
- スタイルが適用されない原因の特定:競合する2つのセレクターを並べて比較
- セレクターを書く前のチェック:詳細度が意図通りか事前に確認
- 学習ツールとして:セレクターを変更しながら数値がどう変わるか観察するのが一番の近道
計算ツールの限界
一般的なセレクターは正確に計算されます。ただし、複雑にネストされた:is()式や、Shadow DOM関連のセレクター(::slotted()、::part())はエッジケースで完全ではない可能性があります。特殊なケースではブラウザのDevToolsで確認するのが確実です。
詳細度を低く保つベストプラクティス
スタイリングにはIDセレクターを使わない IDはJavaScriptのフックやアンカーとして使い、CSSセレクターには使わないことが推奨されています。
セレクターはできるだけ短く
.nav-linkはnav ul li a.nav-linkより良いです。短い方が詳細度が低く、マークアップ変更にも強いです。
BEM等の命名規則を採用する
.card__title--largeのように単一クラスで要素を識別すると、ほぼすべてのセレクターが(0, 1, 0)に統一されます。
!importantはユーティリティクラス専用に
.hiddenや.visually-hiddenなど、常に適用されるべきユーティリティクラスには!importantが正当です。それ以外は再考の余地があります。
関連ツール
- CSS to Tailwind変換ツール — 既存のCSSをTailwindクラスに変換
- CSS Minifier — CSSを最小化・最適化
- CSS Flexboxジェネレーター — フレックスボックスレイアウトをビジュアルで生成
CSS詳細度は一度理解すると、その後の開発体験が大きく変わります。!importantの連鎖から解放され、セレクターが簡潔になり、スタイルが適用されない理由を素早く見つけられるようになります。CSS詳細度計算ツールをデバッグのお供にぜひ活用してください。