- CSS
CSS優先順位と詳細度の早見表|!importantが効かない原因と直し方
CSSの優先順位とは、同じ要素の同じプロパティに複数の指定が当たったとき、どれを採用するかを決めるルール(カスケード)のことです。 判定は「!important かどうか → インライン style かどうか → @layer の順番 → 詳細度 → @scope の近さ → 書いた順番」の順に進み、上の段階で決着がつけば、下の段階は見られません。詳細度(セレクタの強さ)は、この流れの4番目に登場する1つの要素です。
この記事では、セレクタ別の詳細度の一覧と、カスケード全体の判定順のフローチャートを載せ、「!important を付けたのに上書きされない」ケースを1つずつ再現しました。結果はすべて Playwright(Chromium 141.0.7390.37)で getComputedStyle() の値を読み取ったものです。詳細度の数値は、DevTools が内部で使っている仕組み(Chrome DevTools Protocol)から取得した値を載せています。
CSS優先順位の早見表
判定の順番(上から順に比べ、差がついた時点で決まる)
| 順 | 比べるもの | 勝つほう | ひとことメモ |
|---|---|---|---|
| 1 | 重要度(!important) |
!important が付いた宣言 |
アニメーション中の値は通常の宣言より強く、!important より弱い |
| 2 | インライン style かどうか | style="..." 属性の宣言 |
!important 同士でも、インラインが勝つ |
| 3 | @layer(カスケードレイヤー) |
通常:レイヤー外 > 後のレイヤー > 先のレイヤー | !important では順番が逆になる |
| 4 | 詳細度(A-B-C) | ID → クラス → タイプの順に数が多いほう | 同じレイヤー内でだけ比べられる |
| 5 | @scope の近さ |
スコープの根元に近いほう | 詳細度が同じときだけ使われる |
| 6 | 書いた順番 | 後に書いたほう | class 属性の中の並び順は関係しない |
セレクタ別の詳細度(A-B-C 表記)
詳細度は A(ID)- B(クラス・属性・疑似クラス)- C(タイプ・疑似要素) の3つの数で表します。左の桁から比べ、大きいほうが勝ちます。
| セレクタ | 例 | 詳細度 | 数え方 |
|---|---|---|---|
| 全称セレクタ | * |
0-0-0 | 数えない |
| タイプ(要素名) | p |
0-0-1 | C に1 |
| 疑似要素 | p::before |
0-0-2 | 疑似要素も C に1 |
| クラス | .a |
0-1-0 | B に1 |
| 属性 | [data-x]、[type="text"] |
0-1-0 | B に1 |
| 疑似クラス | :hover、:first-child |
0-1-0 | B に1 |
| ID | #t |
1-0-0 | A に1 |
| 結合子 | div p、.wrap > p |
0-0-2、0-1-1 | 空白・>・+・~ は数えない |
:is() |
:is(.a, p)、:is(#x, p) |
0-1-0、1-0-0 | 引数の中でいちばん高いもの |
:not() |
:not(.zzz)、:not(#x) |
0-1-0、1-0-0 | 引数の中でいちばん高いもの |
:has() |
div:has(.a) p |
0-1-2 | 引数の中でいちばん高いもの |
:where() |
:where(#t, .a) |
0-0-0 | 常に0 |
:nth-child() |
p:nth-child(1) |
0-1-1 | 疑似クラスとして B に1 |
:nth-child(n of S) |
p:nth-child(1 of .a) |
0-2-1 | 疑似クラスの1 + S の詳細度 |
| インライン style | style="color: red" |
(別枠) | 詳細度より前の段階で勝つ |
この表の詳細度は、Chromium 141 が各セレクタに対して実際に計算した値です(CSS.getMatchedStylesForNode で取得)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「CSS優先順位と詳細度の早見表|!importantが効かない原因と直し方」
カスケード全体の判定順(フローチャート)
ブラウザは、同じプロパティに当たった複数の宣言を、次の順でふるいにかけます。MDNの「Introduction to the CSS cascade」に書かれている順番を、制作でよく出会う範囲(自分で書くCSS)に絞って図にしました。
flowchart TD
S["同じプロパティに複数の宣言が当たった"] --> R{"セレクタと@mediaは<br>その要素に一致している?"}
R -- 一致しない --> X["候補から外れる"]
R -- 一致する --> I{"!important が<br>付いた宣言がある?"}
I -- ある --> I2["!important 同士で比べる"]
I -- ない --> N["通常の宣言同士で比べる"]
I2 --> IL{"インライン style?"}
IL -- はい --> W1["インラインの !important が勝つ"]
IL -- いいえ --> IY{"@layer の中にある?"}
IY -- ある --> W2["先に宣言したレイヤーが勝つ<br>レイヤー内 > レイヤー外"]
IY -- すべてレイヤー外 --> SP
N --> NL{"インライン style?"}
NL -- はい --> W3["インラインが勝つ"]
NL -- いいえ --> NY{"@layer が違う?"}
NY -- 違う --> W4["レイヤー外 > 後のレイヤー > 先のレイヤー"]
NY -- 同じ --> SP{"詳細度 A-B-C が違う?"}
SP -- 違う --> W5["詳細度が高いほうが勝つ"]
SP -- 同じ --> SC{"@scope の根元までの<br>近さが違う?"}
SC -- 違う --> W6["近いほうが勝つ"]
SC -- 同じ --> W7["後に書いたほうが勝つ"]
全体の強さを1本の列に並べると、次のようになります(上ほど強い)。
| 強さ | 宣言の種類 | 補足 |
|---|---|---|
| 1 | トランジション中の値 | 変化している間だけ |
| 2 | ブラウザ標準CSS・ユーザー設定CSSの !important |
制作側からは上書きできない領域 |
| 3 | インライン style の !important |
|
| 4 | @layer 内の !important(先に宣言したレイヤーほど強い) |
通常の宣言と順番が逆 |
| 5 | レイヤー外の !important |
|
| 6 | アニメーション(@keyframes)の値 |
再生中だけ |
| 7 | インライン style(通常) | |
| 8 | レイヤー外の通常の宣言 | ふだん書いているCSSはここ |
| 9 | @layer 内の通常の宣言(後に宣言したレイヤーほど強い) |
|
| 10 | ユーザー設定CSS、ブラウザ標準CSS(通常) | a 要素の青色など |
| 11 | 親から継承した値 | 直接の指定がないときだけ使われる |
3〜5と7〜9のそれぞれの中で、さらに「詳細度 → @scope の近さ → 書いた順番」で比べます。 大事なのは、詳細度がどれだけ高くても、上の段階(重要度・インライン・レイヤー)で差がついていれば逆転しない、という点です。
実測:段階ごとの勝ち負け
次のHTMLに対して、2つのルールの色を red と blue にして、どちらが採用されるかを確かめました。
<div id="wrap" class="wrap">
<p id="t" class="a b" data-x="1">text</p>
</div>
| 対決 | 採用された色 | 決め手 |
|---|---|---|
.a { red } → .b { blue } |
blue | 詳細度が同じなので後勝ち |
上と同じCSSで class 属性を "b a" に入れ替え |
blue | class 属性内の順番は無関係 |
<style> を2つに分け、1つ目 .a { red }、2つ目 .a { blue } |
blue | 文書内で後に出てくるほうが勝つ |
1つ目 .a.b { red }、2つ目 .a { blue } |
red | 順番より詳細度が先 |
#t { red } 対 style="color: blue" |
blue | インラインは ID より強い |
.a { red !important } 対 style="color: blue" |
red | !important はインラインより強い |
#t { red !important } 対 style="color: blue !important" |
blue | !important 同士ならインラインが勝つ |
.a { red !important } 対 #t { blue } |
red | 重要度が詳細度より先 |
アニメーション(green)対 #t { blue } |
green | 再生中は通常の宣言より強い |
アニメーション(green)対 #t { blue !important } |
blue | !important はアニメーションより強い |
詳細度の数え方と比べ方
桁をまたいで繰り上がらない
詳細度は左の桁から順に比べます。 クラスをいくつ重ねても、ID 1つには届きません。
| 先に書いたセレクタ | 後に書いたセレクタ | 採用されたほう | 詳細度の比較 |
|---|---|---|---|
#t |
.a.b[data-x]:first-child |
先(#t) | 1-0-0 対 0-4-0 |
#t |
.a を256個つなげたもの |
先(#t) | 1-0-0 対 0-256-0 |
.a |
html body div p |
先(.a) | 0-1-0 対 0-0-4 |
.a |
div p |
先(.a) | 0-1-0 対 0-0-2 |
p.a |
.a |
先(p.a) | 0-1-1 対 0-1-0 |
.a.a |
.a |
先(.a.a) | 同じクラスの繰り返しも数える |
[id="t"] |
.a |
後(.a) | 属性セレクタはクラスと同じ 0-1-0。同点なので後勝ち |
#t |
[id="t"] |
先(#t) | 同じ要素を指していても、書き方で詳細度が変わる |
:first-child |
.a |
後(.a) | 疑似クラスも 0-1-0。同点なので後勝ち |
p |
* |
先(p) | * は 0-0-0 |
.wrap p |
.wrap > p |
後 | 結合子は数えない。同点なので後勝ち |
:root p |
html p |
先(:root p) | :root は疑似クラスなので 0-1-1 対 0-0-2 |
「ID は100点、クラスは10点、タイプは1点」という覚え方は、桁が繰り上がらない点だけ補って使うと便利です。クラス10個(0-10-0)は ID 1個(1-0-0)に届かない、と覚えておけば十分です。
:is()・:not()・:has()・:where() の詳細度
この4つは、かっこの中にセレクタを書ける疑似クラスです。自分自身は数えず、引数の詳細度だけが反映されるのが共通のルールで、:where() だけは常に0になります。
| 先 | 後 | 採用されたほう | 理由 |
|---|---|---|---|
:is(#x, p) |
.a.b |
先 | 引数の最大(#x の 1-0-0)が採用される。要素に #x が付いていなくても |
:is(.a, p) |
p |
先 | 0-1-0 対 0-0-1 |
:not(#x) |
.a.b |
先 | :not() も引数の最大が入る(1-0-0) |
p:not(.zzz) |
p.a |
後 | どちらも 0-1-1。同点なので後勝ち |
div:has(.a) p |
div p |
先 | :has() の引数 .a が加わり 0-1-2 |
:where(#t) |
p |
後(p) | :where() は 0-0-0 |
p |
:where(#t) |
先(p) | 後に書いても 0-0-0 は 0-0-1 に届かない |
:is(#x, p) は、p 要素に当たる場合でも詳細度が 1-0-0 になります。 セレクタをまとめる目的で :is() に ID を混ぜると、思ったより強いルールができあがるので、まとめるだけなら :where() のほうが扱いやすい場面があります。
CSSネスト(入れ子)も :is() と同じ扱いです。.a, #zz { & span { color: red } } は :is(.a, #zz) span と同じ意味になり、詳細度は 1-0-1 です。実測でも、後に書いた .a.b span { color: blue }(0-2-1)より優先されて red になりました。
!importantの仕組み
!important は、宣言の値の後ろに付けて「重要度」を上げる目印です。
.note { color: red !important; }
押さえておきたい性質は4つです。
- プロパティ1つずつに付く。 ルール全体ではなく、その1行だけが強くなります。
- !important 同士の勝負は、ふつうの詳細度 → 順番で決まる。 付ければ必ず勝つ、という印ではありません。
- @layer の中では順番が逆になる。 通常はレイヤー外が強いのに対し、!important ではレイヤー内(それも先に宣言したレイヤー)が強くなります。
- 継承には効かない。 親に付けた !important は、子が受け継ぐ値を強くはしません。
!importantを付けても上書きされない9つのケース
「!important を付けたのに変わらない」を、実際に再現して確かめました。期待は red、実際の結果が blue などになっているものが「効いていない」ケースです。
| # | 書いたCSS(red にしたい) | 実測 | 原因 |
|---|---|---|---|
| 1 | .a { color: red !important } と #t { color: blue !important } |
blue | 相手も !important で、詳細度が上 |
| 2 | .a { color: red !important } → .b { color: blue !important } |
blue | 相手も !important で、同じ詳細度・後ろにある |
| 3 | #t { color: red !important } と style="color: blue !important" |
blue | 相手がインラインの !important |
| 4 | #t { color: red !important } と、@layer a { p { color: blue !important } } |
blue | レイヤー内の !important はレイヤー外より強い |
| 5 | #t { color: red !importnat } |
blue | つづりの誤りで、その宣言ごと無効になる |
| 6 | #t { color: redd !important } |
blue | 値が無効で、その宣言ごと無効になる |
| 7 | .wrap .c { color: red !important }、@media (max-width: 500px) { ... } |
blue | セレクタや @media が一致していない |
| 8 | 親 .wrap { color: red !important } と子 p { color: blue } |
blue | 親の値は継承で届くだけ。子への直接指定が優先 |
| 9 | .a { --c: red !important; color: var(--c) } と #t { color: blue } |
blue | !important が付いたのは変数 --c。color の宣言は通常のまま |
順に直し方を見ていきます。
1・2:相手も !important のとき
!important 同士では、通常と同じく詳細度と順番で決まります。相手の !important を外せるなら外すのがいちばんすっきりします。外せない場合(外部のCSSなど)は、同じ詳細度のセレクタを後ろに書くか、次の章の @layer を使います。
/* 直す前 */
.a { color: red !important; }
#t { color: blue !important; } /* 詳細度 1-0-0 で勝つ */
/* 直した後:相手の !important を外せば、こちらは通常の宣言で足りる場合が多い */
#t { color: blue; }
.a { color: red !important; } /* 重要度で勝つ → red */
3:インライン style や JavaScript が相手のとき
style="color: blue !important" は、スタイルシート側の !important より強くなります。JavaScript が書き込むインライン style も同じ扱いです。
| JavaScript の書き方 | スタイルシート側 .a { color: blue !important } に対して |
|---|---|
el.style.color = 'red' |
blue のまま(通常のインラインは !important に届かない) |
el.style.setProperty('color', 'red', 'important') |
red になる(インラインの !important) |
スライダーやモーダルのライブラリがインライン style を書き込んでいる場合、CSS 側で上書きするには !important が必要になります。ライブラリ側にクラス名やCSS変数で見た目を変える設定がないかを先に確かめると、!important を使わずに済むことがあります。
4:@layer の中の !important が相手のとき
通常の宣言ではレイヤー外が勝ちますが、!important ではレイヤー内が勝ちます。
| 対決 | 実測 |
|---|---|
@layer a { p { red !important } } 対 レイヤー外 #t { blue !important } |
red(レイヤー内が勝つ) |
@layer a, b; で a 内 p { red !important } 対 b 内 #t { blue !important } |
red(先に宣言した a が勝つ) |
@layer a { p { red !important } } 対 style="color: blue !important" |
blue(インラインは レイヤーより前の段階) |
レイヤー内の !important を上書きしたいときは、それより先に宣言したレイヤーを用意して、そこに !important を書きます。
@layer override, vendor; /* override を先に宣言 */
@layer vendor { p { color: blue !important; } }
@layer override { p { color: red !important; } } /* → red */
5・6・7:宣言そのものが届いていないとき
つづりや値に誤りがあると、ブラウザはその1行を読み飛ばします。エラー表示は出ないので、DevTools で確かめるのが近道です(次の章)。なお ! important のように ! の後ろに空白が入る書き方は有効で、実測でも red になりました。
8:親に付けた !important
!important は「その要素の、そのプロパティ」を強くするだけで、子へ渡る値を強くするわけではありません。 継承した値は、子に直接当たる宣言があれば、それがどんなに弱いセレクタでも負けます。
| 対決 | 実測 | 理由 |
|---|---|---|
親 #wrap { color: red !important }、子に指定なし |
red | 継承で届く |
親 #wrap { color: red !important } と * { color: blue } |
blue | *(0-0-0)でも、子への直接指定が継承より優先 |
親 .wrap { color: red !important } の中の a 要素 |
rgb(0, 0, 238) | ブラウザ標準CSSの a の色が、継承より優先 |
直すには、変えたい要素そのものに当たるセレクタで書きます。
/* 直す前 */ .wrap { color: red !important; }
/* 直した後 */ .wrap a { color: red; } /* または .wrap a { color: inherit; } */
9:カスタムプロパティに付けた !important
--c: red !important の !important は、変数 --c をめぐる勝負にだけ効きます。その変数を使う color: var(--c) の宣言は通常の重要度のままです。逆に、変数同士の勝負では効きます。.a { --c: red !important } と #t { --c: blue } があるとき、p { color: var(--c) } は red になりました。
補足:アニメーションとトランジション
@keyframesの中に書いた !important は無視されます。 実測でも、keyframes 内のcolor: green !importantは採用されず、#t { color: blue }の blue になりました。- アニメーション再生中の値を止めたいときは、通常のルール側に !important を付けます。
- トランジションで変化している間は、その途中の値が最優先になります。
transition: color 100sを付けた要素に !important の色を当てた直後は、まだ変化前の色のままでした。
DevToolsで打ち消し線から原因を特定する手順
Chrome DevTools の Elements パネルを使うと、どの宣言が採用され、どれが上書きされたかを確かめられます。以下は DevTools の一般的な操作手順で、画面の操作そのものは今回は未実行です(表示のもとになるデータは、次の項で実際に取得しています)。
| 手順 | 操作 | わかること |
|---|---|---|
| 1 | 対象の要素を右クリック →「検証」 | Elements パネルでその要素が選ばれる |
| 2 | Styles タブを見る | 当たっているルールが、強い順に上から並ぶ |
| 3 | 打ち消し線の付いた宣言を探す | その宣言は、より上にある別の宣言に上書きされている |
| 4 | Styles の上部の Filter に color などのプロパティ名を入れる |
そのプロパティに関係する宣言だけに絞れる |
| 5 | Computed タブでプロパティ名の横の矢印を開く | 採用された宣言と、上書きされた宣言の一覧が出る |
| 6 | セレクタにマウスを重ねる | 詳細度が表示される |
| 7 | 宣言の左に警告マークが出ていないか見る | 値やプロパティ名が無効なときに出る |
| 8 | ルールが Layer の見出しの下にあるか見る |
@layer に入っているかどうか |
打ち消し線が付いた宣言と、いちばん上で打ち消されていない宣言を見比べ、早見表の「判定の順番」を上から当てはめます。
| 見比べてわかったこと | 原因の段階 |
|---|---|
| 勝っているほうに !important がある | 重要度 |
勝っているほうが element.style の欄にある |
インライン style(JavaScript の書き込みを含む) |
| 自分のルールが Layer の下にあり、相手は外にある | @layer |
| 相手のセレクタのほうが ID やクラスの数が多い | 詳細度 |
| セレクタの強さが同じで、相手のファイルや行が後ろ | 書いた順番(読み込み順) |
| 自分のルールが一覧に出てこない | セレクタの不一致・@media の不一致・ファイルの未読み込み |
| 一覧にはあるが警告マーク付き | つづり・値の誤り |
Inherited from ... の欄にしか出てこない |
継承で届いているだけ(ケース8) |
Consoleで「当たっている宣言」を一覧にするスニペット
Styles タブと同じ情報を、プロパティを1つに絞って一覧にするスニペットです。#t と color を調べたい対象に書き換えて使います。
const el = document.querySelector('#t'), prop = 'color', rows = [];
const walk = (rules, ctx) => {
for (const r of rules) {
if (r instanceof CSSStyleRule) {
if (el.matches(r.selectorText) && r.style.getPropertyValue(prop)) {
rows.push({ selector: r.selectorText, value: r.style.getPropertyValue(prop),
important: r.style.getPropertyPriority(prop) === 'important', within: ctx });
}
if (r.cssRules?.length) walk(r.cssRules, ctx);
} else if (r.cssRules) {
walk(r.cssRules, (ctx + ' ' + r.cssText.split('{')[0].trim()).trim());
}
}
};
for (const s of document.styleSheets) { try { walk(s.cssRules, ''); } catch (e) {} }
console.table(rows);
console.log('採用された値:', getComputedStyle(el)[prop]);
次のCSSを当てたページで実行した結果です(Playwright から同じコードを実行。console.table の代わりに配列の中身を表にしています)。
p { color: black }
.a { color: blue }
.wrap .a { color: red !important }
#t { color: green }
@layer x { p.a { color: orange } }
| selector | value | important | within | 詳細度(Chromium の計算値) |
|---|---|---|---|---|
| p | black | false | 0-0-1 | |
| .a | blue | false | 0-1-0 | |
| .wrap .a | red | true | 0-2-0 | |
| #t | green | false | 1-0-0 | |
| p.a | orange | false | @layer x | 0-1-1 |
採用された値は rgb(255, 0, 0)(red)でした。#t のほうが詳細度は高いのに、!important の付いた .wrap .a が勝っていることが一覧から読み取れます。別ドメインから読み込んだCSSは、ブラウザの制限で cssRules を読めないため、この一覧には出てきません(try で読み飛ばしています)。
詳細度を上げずに直す書き方
上書きしたいとき、セレクタを長くしたり !important を足したりすると、次に上書きする人がさらに強い指定を書くことになります。相手を弱くする、層の順番で勝つ、必要な分だけ上回る、という方向の直し方を5つ紹介します。いずれも red を blue に上書きできることを確かめました。
| 方法 | 書き方 | 実測 |
|---|---|---|
| A. :where() で相手を弱くする | :where(.wrap) p { red } → .a { blue } |
blue |
| B. リセットCSSを @layer に入れる | @layer reset { #wrap p { red } } → .a { blue } |
blue |
| C. 外部CSSを @layer に入れる | @layer vendor { .wrap .a { red } } → .a { blue } |
blue |
| D. カスタムプロパティで値だけ渡す | .wrap p { color: var(--c, red) } → .a { --c: blue } |
blue |
| E. 相手と同じ形にそろえ、クラス1つぶんだけ上回る | .wrap p { red }(0-1-1)→ .wrap .a { blue }(0-2-0) |
blue |
| 比較:クラスを重ねる | #wrap p { red } → .a.a.a { blue } |
red(ID には届かない) |
A. :where() で土台のスタイルを弱くしておく
共通部品やリセットのように「あとで上書きされる前提」のスタイルは、:where() で包んでおくと、利用する側はクラス1つで上書きできます。
/* 土台:詳細度 0-0-1(:where の中は数えない) */
:where(.article) a { color: blue; text-decoration: underline; }
/* 利用側:クラス1つ(0-1-0)で上書きできる */
.button { color: white; text-decoration: none; }
B・C. @layer で「層の順番」を先に決める
@layer は、CSSを層に分け、層の順番で優先度を決める仕組みです。同じ層の中でだけ詳細度が比べられ、層が違えば詳細度は見られません。
/* 1行目で順番を宣言する。後ろほど強い */
@layer reset, vendor, components, utilities;
@layer reset { #main p { margin: 0; } } /* ID を使っていても… */
@layer components { .lead { margin: 1em 0; } } /* 後の層のクラス1つが勝つ */
実測で確かめたレイヤーの性質をまとめます。
| 確かめたこと | 実測 |
|---|---|
レイヤー内 #t { red } 対 レイヤー外 p { blue } |
blue(レイヤー外が強い) |
| レイヤー外のルールを先に書いた場合 | blue(位置にかかわらずレイヤー外が強い) |
@layer a, b; のとき a 内 #t { red } 対 b 内 p { blue } |
blue(後の層が強い) |
| b のブロックを a より先に書いた場合 | blue(最初の @layer a, b; の順番が使われる) |
名前のないレイヤー @layer { #t { red } } 対 レイヤー外 p { blue } |
blue |
レイヤーの入れ子:a 直下 #t { red } 対 a の中の x 内 p { blue } |
red(その層の直下は、入れ子の層より強い) |
color: blue; color: revert-layer; |
1つ前の層の値(red)に戻る |
外部のCSSファイルを層に入れるときは、@import url("vendor.css") layer(vendor); と書きます(この @import の書き方は今回は未実行で、同じ内容を @layer vendor { ... } のブロックで確かめています)。自分のCSSをレイヤー外か後ろの層に置けば、外部CSSのセレクタがどれだけ長くても、短いセレクタで上書きできます。
D. カスタムプロパティで値だけを差し替える
部品側が var(--c, red) のように変数で値を受け取る形にしておくと、利用側はセレクタの強さを競わずに値を変えられます。変数は継承されるので、親やその要素自身のどこかで --c を定義すれば届きます。
@scope:範囲を区切り、近いほうを優先する
@scope は、ルールが効く範囲を「この要素の中だけ」と区切る仕組みです。MDNでは Baseline 2026(2026年3月から主要ブラウザの現行版で利用可能)と案内されています。
@scope (.card) to (.card__body) { /* .card の中、ただし .card__body より内側は除く */
p { color: red; }
}
カスケードでは、詳細度が同じときに「スコープの根元に近いほう」が勝つという段階が加わります。次のHTMLで確かめました。
<div class="card"><div class="note"><p id="t" class="a">t</p></div></div>
| 対決 | 実測 | 理由 |
|---|---|---|
@scope (.card) { p { red } } → @scope (.note) { p { blue } } |
blue | .note のほうが p に近い |
| 上の2つを逆の順番で書く | blue | 順番より近さが先 |
@scope (.card) { p.a { red } } 対 @scope (.note) { p { blue } } |
red | 近さより詳細度が先 |
@scope (.card) { p { red } } → スコープなしの p { blue } |
red | 同じ詳細度なら、スコープ付きが優先 |
スコープなしの .a { blue } → @scope (.card) { p { red } } |
blue | 詳細度(0-1-0 対 0-0-1)が先 |
@scope (.card) to (.note) { p { red } } |
適用されない | p は .note より内側なので範囲外 |
@layer a { @scope (.wrap) { p { red } } } 対 後の層 @layer b { p { blue } } |
blue | レイヤーが近さより先 |
@scope の中のセレクタには、スコープの根元(.card)の詳細度は加算されません。@scope (#wrap) { p { ... } } の p は 0-0-1 のままで、実測でもスコープ外の .a に上書きされました。根元の詳細度も加えたいときは :scope p(0-1-1)と書きます。
!importantを使う場面の判断表
!important は、使いどころを決めておくと便利な道具です。
| 場面 | 使うか | 理由と書き方 |
|---|---|---|
余白や表示切り替えのユーティリティクラス(.hidden、.mt-0 など) |
使う | 「付けたら必ず効く」ことが役目。.hidden { display: none !important; } |
| 動きを減らす設定への対応 | 使う | @media (prefers-reduced-motion: reduce) の中で、すべてのアニメーションを止める |
| ライブラリが書き込むインライン style の上書き | 使う(ほかに手段がないとき) | 設定やCSS変数で変えられないかを先に確認 |
| 編集できない外部CSSの上書き | できれば @layer | 外部CSSを層に入れ、自分のCSSを外に置く |
| 自分のCSS同士の上書き | 使わない | セレクタの詳細度をそろえ、順番で解決する |
| 「とりあえず効かせたい」 | 使わない | DevTools で原因の段階を特定してから直す |
上書きされないときのチェックリスト
| 確認 | 見る場所 |
|---|---|
| 自分のルールが Styles に出ているか | 出ていなければセレクタ・@media・ファイルの読み込み |
| 警告マークが付いていないか | つづり・値 |
| 相手に !important があるか | あれば重要度の段階で決着している |
相手が element.style か |
インライン style・JavaScript |
| どちらかが Layer の中か | @layer の順番 |
| 詳細度はどちらが高いか | セレクタにマウスを重ねる |
| 読み込み順はどちらが後か | ファイル名と行番号 |
| 継承で届いているだけではないか | Inherited from の欄 |
よくある質問
Q. !important とインライン style はどちらが強いですか?
A. スタイルシートの !important のほうが、通常のインライン style より強いです。ただし、インライン style 側にも !important が付いている場合はインラインが勝ちます。実測でも、.a { color: red !important } 対 style="color: blue" は red、#t { color: red !important } 対 style="color: blue !important" は blue でした。
Q. CSSファイルの読み込み順は優先順位に関係しますか?
A. 関係します。ただし見られるのは最後の段階で、重要度・インライン・レイヤー・詳細度がすべて同じときだけです。<style> を2つに分けた実測では、同じ .a 同士なら後の <style> が勝ち、1つ目が .a.b(詳細度が上)なら1つ目が勝ちました。
Q. :is() と :where() はどう使い分けますか? A. セレクタをまとめる働きは同じで、詳細度が違います。:is() は引数の中でいちばん高い詳細度になり、:where() は常に 0-0-0 です。あとで上書きされる前提の土台のスタイルには :where()、いまの強さを保ちたいところには :is() が向いています。
Q. 詳細度を確認できるツールはありますか? A. Chrome DevTools の Styles タブでセレクタにマウスを重ねると表示されます。この記事の表の値は、DevTools が使っているのと同じ仕組みから取得しました。
まとめ
- 優先順位は「重要度 → インライン → @layer → 詳細度 → @scope の近さ → 書いた順番」の順に判定され、上の段階で決まれば下は見られない。
- 詳細度は A-B-C の3桁で、左から比べる。 クラスを256個重ねても ID 1個には届かない。
- :is()・:not()・:has() は引数の最大の詳細度、:where() は常に0。
- !important 同士は、詳細度と順番で決まる。 インラインの !important と、@layer 内の !important はさらに強い。
- 親に付けた !important は子の直接指定に届かない。 変えたい要素に当たるセレクタで書く。
- 原因は DevTools の打ち消し線と、判定順の表を上から当てはめて特定する。
- 直すときは :where()・@layer・カスタムプロパティで、詳細度を上げずに解決する。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「CSS優先順位と詳細度の早見表|!importantが効かない原因と直し方」
株式会社RJCでは、CSSの優先順位と詳細度の仕組みを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。