CSSセレクタとは、「どの要素にスタイルを当てるか」をHTMLの中から選び出すための書き方です。 型・クラス・IDの基本から、要素どうしの関係を表す結合子、[href$=".pdf" i] のような属性の条件、:nth-child() や :focus-visible などの擬似クラス、::before などの擬似要素、:has() とネスト(&)まで押さえると、JavaScriptに頼らずに済む場面がぐっと増えます。

この記事では、すべてのセレクタを「構文 / 意味 / 例 / 詳細度 / 対応状況」の早見表 にまとめ、Playwright + Chromium 141 で「各セレクタが一致した要素数」「ルールが競合したときの getComputedStyle の値」「フォーカスやフォーム状態の変化」を実測 しました。対応状況は MDN の browser-compat-data 8.1.3 と web-features 3.40.0(2026年9月時点)で確認しています。よくあるつまずき(子孫と子、:nth-child と :nth-of-type、LVHA の順序、二重コロン)と、コピペで使える実務パターン集も載せています。


CSSセレクタ早見表(詳細度・対応状況つき)

詳細度は「ID の数 - クラス等の数 - 型の数」で表記します。対応状況は Chrome / Firefox / Safari の対応開始バージョンで、◎ は 2020 年以前から主要ブラウザすべてで使えるもの です。

基本セレクタと結合子

構文 意味 例 詳細度 対応状況
* すべての要素 * { box-sizing: border-box } 0-0-0 ◎
E 型(タグ名) li { margin: 0 } 0-0-1 ◎
.class クラス .item { padding: 8px } 0-1-0 ◎
#id ID #name { width: 100% } 1-0-0 ◎
E.a.b 複合(すべての条件を満たす) li.item.current 0-2-1 ◎
A, B セレクタリスト(いずれか) h2, h3 { color: navy } それぞれ個別 ◎
A B 子孫結合子(A の中のすべての B) article p 合算(0-0-2) ◎
A > B 子結合子(A の直下の B) article > p 合算 ◎
A + B 次兄弟結合子(A の直後の B) h2 + p 合算 ◎
A ~ B 後続兄弟結合子(A より後ろの兄弟 B) h2 ~ p 合算 ◎
A || B 列結合子(表の列に属するセル) col.total || td 合算 未対応(Chromium 141 で SyntaxError。Firefox / Safari も未実装)

属性セレクタ

構文 意味 例 詳細度 対応状況
[attr] 属性がある [required] 0-1-0 ◎
[attr="v"] 値が完全一致 [type="checkbox"] 0-1-0 ◎
[attr~="v"] 空白区切りの語のひとつが v [class~="current"] 0-1-0 ◎
[attr|="v"] v または v- で始まる(言語コード向け) [lang|="en"] 0-1-0 ◎
[attr^="v"] v で始まる [href^="https://"] 0-1-0 ◎
[attr$="v"] v で終わる [href$=".pdf"] 0-1-0 ◎
[attr*="v"] v を含む [href*="example"] 0-1-0 ◎
[attr="v" i] 大文字小文字を区別しない [href$=".pdf" i] 0-1-0 Chrome 49・Firefox 47・Safari 9
[attr="v" s] 区別を強制する [data-key="A" s] 0-1-0 Firefox 66・Safari 27。Chromium 未対応(SyntaxError)

構造擬似クラス(位置・ツリー)

構文 意味 例 詳細度 対応状況
:root 文書のルート(html) :root { --gap: 8px } 0-1-0 ◎
:first-child / :last-child 最初 / 最後の子 li:first-child 0-1-0 ◎
:nth-child(An+B) 兄弟の中で An+B 番目(odd / even も可) tr:nth-child(even) 0-1-0 ◎
:nth-last-child(An+B) 後ろから数えて An+B 番目 li:nth-last-child(1) 0-1-0 ◎
:nth-child(An+B of S) S に当てはまる兄弟だけを数える li:nth-child(2 of .done) 0-1-0 + S の最高値 Chrome 111・Firefox 113・Safari 9(Baseline 2023)
:first-of-type / :last-of-type 同じ型の中で最初 / 最後 p:first-of-type 0-1-0 ◎
:nth-of-type(An+B) / :nth-last-of-type() 同じ型の中で An+B 番目 p:nth-of-type(2) 0-1-0 ◎
:only-child / :only-of-type 唯一の子 / 同じ型で唯一 .box p:only-child 0-1-0 ◎
:empty 子要素もテキストもない .note:empty { display: none } 0-1-0 ◎

状態擬似クラス(ユーザー操作・リンク)

構文 意味 例 詳細度 対応状況
:hover ポインタが乗っている(祖先にも当たる) a:hover 0-1-0 ◎
:active 押している最中 button:active 0-1-0 ◎
:focus フォーカスがある input:focus 0-1-0 ◎
:focus-visible フォーカスリングを表示すべきとき(キーボード操作など) button:focus-visible 0-1-0 Chrome 86・Firefox 85・Safari 15.4(Baseline 2022)
:focus-within 自分か子孫にフォーカスがある form:focus-within 0-1-0 Chrome 60・Firefox 52・Safari 10.1
:link / :visited 未訪問 / 訪問済みリンク a:link 0-1-0 ◎(:visited は色系のみ変更可)
:target URL の #id に一致する要素 section:target 0-1-0 ◎

入力・フォームの擬似クラス

構文 意味 例 詳細度 対応状況
:checked チェック / 選択されている input:checked + label 0-1-0 ◎
:indeterminate 未確定(JS で設定したチェックボックス、選択なしのラジオ群、値なしの progress) input:indeterminate 0-1-0 ◎
:disabled / :enabled 無効 / 有効 button:disabled 0-1-0 ◎
:required / :optional 必須 / 任意 input:required 0-1-0 ◎
:valid / :invalid 制約検証に合格 / 不合格(読み込み直後から判定) input:invalid 0-1-0 ◎
:user-valid / :user-invalid ユーザーが操作した後だけ判定 input:user-invalid 0-1-0 Chrome 119・Firefox 88・Safari 16.5(Baseline 2023)
:placeholder-shown プレースホルダが表示中(未入力) input:placeholder-shown 0-1-0 Chrome 47・Firefox 51・Safari 9
:in-range / :out-of-range min / max の範囲内 / 外 input:out-of-range 0-1-0 ◎
:read-only / :read-write 編集不可 / 可(:read-only は入力欄以外にも一致) input:read-only 0-1-0 ◎

論理擬似クラス

構文 意味 例 詳細度 対応状況
:is(A, B) いずれかに一致(無効なセレクタは無視される) :is(h1, h2, h3) a 引数の中で最も高いもの Chrome 88・Firefox 78・Safari 14
:where(A, B) :is() と同じ一致で、詳細度が常に 0 :where(ul, ol) { padding: 0 } 0-0-0 Chrome 88・Firefox 78・Safari 14
:not(A, B) いずれにも一致しない li:not(.done, .hidden) 引数の中で最も高いもの ◎(複数引数は Chrome 88・Firefox 84・Safari 9)
:has(A) A に当てはまる要素を中(または後ろ)に持つ label:has(+ input:required) 引数の中で最も高いもの Chrome 105・Firefox 121・Safari 15.4(2026年6月に Baseline Widely available)

:has() は別記事「CSS :has() 使い方」で15パターンを検証しているため、この記事では表と実務パターンの1例に留めます。

擬似要素

構文 意味 例 詳細度 対応状況
::before / ::after 要素の先頭 / 末尾に生成する内容(content 必須) .req::after { content: " *" } 0-0-1 ◎(単一コロン :before も互換のため可)
::first-line / ::first-letter 最初の行 / 最初の文字 p::first-letter { font-size: 2em } 0-0-1 ◎
::placeholder プレースホルダ文字 input::placeholder { color: gray } 0-0-1 Chrome 57・Firefox 51・Safari 10.1
::marker リストの行頭記号 li::marker { color: navy } 0-0-1 Chrome 86・Firefox 68・Safari 11.1(Safari は color と font-size のみ)
::selection 選択範囲 ::selection { background: yellow } 0-0-1 Chrome 1・Firefox 62・Safari 1.1(iOS Safari は未対応)
::backdrop モーダルダイアログや全画面要素の背面 dialog::backdrop 0-0-1 Chrome 37・Firefox 47・Safari 15.4
::file-selector-button input type="file" のボタン input::file-selector-button 0-0-1 Chrome 89・Firefox 82・Safari 14.1

ネスト(&)

書き方 展開されるセレクタ 詳細度 対応状況
.card { .title {} } .card .title(& 省略時は子孫) 0-2-0 Chrome 120・Firefox 117・Safari 17.2(2026年6月に Baseline Widely available)
.card { &.active {} } .card.active(複合) 0-2-0 同上
.card { .dark & {} } .dark .card(& を後ろに置く) 0-2-0 同上
.card { & + & {} } .card + .card 0-2-0 同上
.card { p {} } .card p(型セレクタ始まりも可) 0-1-1 Chrome 120・Safari 17.2 以降(それ以前は & p が必要)
.card { @media (...) {} } @media の中に .card 0-1-0 同上
#a, .b { & p {} } :is(#a, .b) p と同じ扱い 1-0-1(リストの最高値) 同上

その他の擬似クラス

構文 意味 例 詳細度 対応状況
:lang(ja) 言語(lang 属性の継承を含む) :lang(en) q { quotes: '"' '"' } 0-1-0 ◎(:lang(en, fr) の複数指定は Firefox 114・Safari 9。Chromium 未対応)
:dir(rtl) 文字方向(dir 属性や継承で決まる意味上の方向) :dir(rtl) .icon { transform: scaleX(-1) } 0-1-0 Chrome 120・Firefox 49・Safari 16.4(Baseline 2023)
:defined 定義済みの要素(未定義のカスタム要素は非一致) my-widget:not(:defined) { visibility: hidden } 0-1-0 Chrome 54・Firefox 63・Safari 10
:modal showModal() で開いたダイアログ dialog:modal { border: 0 } 0-1-0 Chrome 105・Firefox 103・Safari 15.6
:popover-open 表示中のポップオーバー [popover]:popover-open 0-1-0 Chrome 114・Firefox 125・Safari 17
:open 開いている dialog / details / select など details:open 0-1-0 Chrome 133・Firefox 136・Safari 26.5(Baseline 2026)
:state(name) カスタム要素が ElementInternals.states に追加した状態 my-widget:state(loaded) 0-1-0 Chrome 125・Firefox 126・Safari 17.4(Baseline 2024)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「CSSセレクタ一覧|:has()・ネスト対応の早見表と詳細度」


検証環境と、一致数を数えるサンプルHTML

項目 内容
ブラウザ Chromium 141.0.7390.37(Playwright 1.56.1、Node.js 22.22)
確認方法 document.querySelectorAll(セレクタ).length で一致数、CSS.supports("selector(...)") で構文対応、getComputedStyle() で適用結果
対応状況の出典 @mdn/browser-compat-data 8.1.3、web-features 3.40.0

次のHTMLを index.html として保存すると、下の表の数字をそのまま再現できます。

<!doctype html>
<html lang="ja">
<head><meta charset="utf-8"><title>CSS selector test</title></head>
<body>
<nav id="menu">
  <ul class="nav">
    <li class="item"><a href="#top">トップ</a></li>
    <li class="item current"><a href="/orders" data-count="3">注文一覧</a></li>
    <li class="item"><a href="https://example.com/guide.PDF" target="_blank">資料 (PDF)</a></li>
    <li class="item"><a href="https://example.com/help">ヘルプ</a></li>
  </ul>
</nav>
<main>
  <article class="post" lang="en">
    <h2 id="title">Selector test page</h2>
    <p class="lead">Lead paragraph</p>
    <p>Second paragraph with <span>span</span> and <strong>strong</strong></p>
    <ul class="list">
      <li>A</li><li class="important">B</li><li>C</li><li class="important">D</li><li>E</li>
    </ul>
    <p></p>
    <div class="box"><p>nested p in div</p></div>
  </article>
  <form id="order-form" novalidate>
    <label for="name">お名前</label>
    <input id="name" name="name" type="text" required placeholder="山田 太郎">
    <input id="qty" name="qty" type="number" min="1" max="10" value="3">
    <input id="qty2" name="qty2" type="number" min="1" max="10" value="20">
    <input id="agree" type="checkbox">
    <input id="all" type="checkbox">
    <input id="r1" type="radio" name="size" value="s" checked>
    <input id="r2" type="radio" name="size" value="m">
    <input id="memo" type="text" disabled value="x">
    <input id="ro" type="text" readonly value="y">
    <input id="email" type="email" value="not-an-email">
    <input id="file" type="file">
    <button id="submit" type="submit">送信</button>
    <button id="btn" type="button">下書き保存</button>
  </form>
  <script>document.getElementById("order-form").addEventListener("submit", e => e.preventDefault());</script>
  <table class="orders">
    <thead><tr><th>ID</th><th>品名</th></tr></thead>
    <tbody><tr><td>1</td><td>備品A</td></tr><tr><td>2</td><td>備品B</td></tr><tr><td>3</td><td>備品C</td></tr><tr><td>4</td><td>備品D</td></tr></tbody>
  </table>
  <p id="rtl" dir="rtl">مرحبا</p>
  <my-widget id="w1"></my-widget>
  <dialog id="dlg"><p>dialog</p></dialog>
  <div id="pop" popover>popover</div>
  <section id="top"><h3>top</h3></section>
</main>
</body>
</html>

一致数は Playwright で開いたページ上で document.querySelectorAll(sel).length を実行して数えました(npm i playwright@1.56 → const { chromium } = require('playwright') → page.goto('file://.../index.html') → page.evaluate(...))。構文エラーになるセレクタは try { ... } catch (e) { e.name + ': ' + e.message } で捕まえています。

基本・結合子・属性・構造擬似クラスの一致数(Chromium 141)

セレクタ 意味 一致数
h2, h3 セレクタリスト 2(CSS.supports("selector(h2, h3)") は false。selector() には単一のセレクタしか渡せない)
article p 子孫(.box の中の p も含む) 4
article > p 子(直下だけ) 3
thead || td 列結合子 エラー(下のエラー文)
[class~="current"] / [lang|="en"] 空白区切りの1語 / en または en- 始まり 1 / 1
[href$=".pdf"] 後方一致(guide.PDF は大文字なので不一致) 0
[href$=".pdf" i] 大文字小文字を区別しない 1
[href$=".pdf" s] 区別を強制 エラー(SyntaxError)
.list li:nth-child(2 of .important) .important の中で2番目(D) 1
.list li.important:nth-child(2) 兄弟全体で2番目かつ .important(B) 1
article p:nth-of-type(2) p の中で2番目 1
article p:nth-child(2) 2番目の子(h2 の次)がたまたま p なので一致 1
article p:first-of-type article 直下と .box の中、それぞれの最初の p 2

列結合子のエラー文はそのまま次のとおりでした。

SyntaxError: Failed to execute 'querySelectorAll' on 'Document': 'thead || td' is not a valid selector.

状態・入力・その他の擬似クラス(読み込み直後)

セレクタ 一致数 補足
a:link / a:visited 4 / 0 querySelectorAll(':visited') はプライバシー保護のため常に 0
:valid / :invalid 8 / 4 :invalid は name・qty2・email と form 自身
input:user-invalid 0 操作前は一致しない(次の表)
:read-only / :read-write 68 / 4 :read-only は編集できない要素すべて(div や p も)に一致
p::before 0 擬似要素は querySelectorAll では取れない(エラーにはならない)
:lang(en) / :dir(rtl) 15 / 1 article lang="en" の子孫が 15

操作で変わる擬似クラスの実測(操作前 → 操作後)

:hover や :focus-visible は「いつ一致するか」が分かりにくいので、Playwright で操作して一致要素の id を記録しました。

セレクタ 操作 操作前 操作後
li:hover page.hover('li.current') なし li
:focus マウスでボタン #btn をクリック なし btn
:focus-visible マウスでボタン #btn をクリック なし なし(ボタンのクリックではリングを出さない)
:focus-visible マウスでテキスト入力 #name をクリック なし name(入力欄は常に表示)
:focus-within 同上 なし html, body, main, order-form, name
:focus-visible #btn クリック後、Shift+Tab で #submit へ なし submit(キーボード移動では表示)
:target URL を index.html#top に変更 なし top
input:indeterminate all.indeterminate = true なし all
:user-invalid #name に "a" を入力して消し、フォーカスを外す なし name
:user-invalid さらに送信ボタンを押す(送信を試みる) name, qty2, email(全欄が対象になる)
:user-valid #qty を 5 にしてフォーカスを外す なし qty
:modal dlg.showModal() なし dlg(dialog:open にも一致)
:modal dlg.show()(非モーダル) なし(dialog[open] には一致)
:popover-open pop.showPopover() なし pop
my-widget:state(loaded) 定義内で this.attachInternals().states.add('loaded') なし w1

:invalid は読み込み直後から一致するのに対し、:user-invalid は「ユーザーが触った後」または「送信を試みた後」だけ一致します。 入力前からエラー表示になるのを避けたいフォームでは :user-invalid を選ぶとよいです。


詳細度の計算表と、競合したときにどれが勝つか

詳細度(specificity)は「ID の数、クラス・属性・擬似クラスの数、型・擬似要素の数」の3桁で比べ、左の桁から大きいほうが勝ちます。 同じ詳細度なら後に書いたルールが勝ちます。

セレクタ ID クラス等 型等 表記
* 0 0 0 0-0-0
p / p::before 0 0 1 / 2 0-0-1 / 0-0-2
.a / [type="text"] / :hover 0 1 0 0-1-0
p.a 0 1 1 0-1-1
.a.b / .a.a(同じクラスの繰り返しも数える) 0 2 0 0-2-0
#name 1 0 0 1-0-0
nav ul li a 0 0 4 0-0-4
:is(#a, .b) / :not(#a) / :has(#a) 1 0 0 1-0-0(引数の最高値)
:where(#a, .b) 0 0 0 0-0-0
li:nth-child(2 of .done) 0 2 1 0-2-1(0-1-0 + S の最高値)
#a, article { & p {} } の & p 1 0 1 1-0-1
style="..."(インライン) — — — すべてのセレクタより優先
!important — — — インラインよりさらに優先(!important 同士は詳細度で比較)

Chromium 141 で実測した勝敗

次のCSSとHTMLで、どのルールの色が getComputedStyle().color に残るか を確認しました。

/* 1: 型 < クラス < ID(書いた順に関係なく) */
#t1 { color: blue; }  .a { color: green; }  p { color: red; }
/* 2: :is() は引数の中で最も高い詳細度(#t2 → 1-0-0) */
:is(#t2, .x) { color: blue; }  .x.x { color: red; }  p.x.x.x { color: red; }
/* 3: :where() は 0-0-0 */
:where(#t3, .y) { color: blue; }  p.t3 { color: red; }
/* 4: インライン > ID(HTML 側は style="color: red") */
#t4 { color: blue; }
/* 5: !important > インライン */
p.t5 { color: blue !important; }
/* 6: ネストの & は親リストの最高値を取る(#nothing は存在しない ID) */
#nothing, article { & p { color: blue; } }   /* 1-0-1 */
.post p { color: red; }                      /* 0-1-1 */
/* 7: :not() の中身も数える */
.w8 p:not(#never) { color: blue; }  /* 1-1-1 */  #t8 { color: red; }  /* 1-0-0 */
/* 8: 同じ詳細度なら後勝ち */
.a10 { color: blue; }  .b10 { color: red; }
/* 9: @layer の中は層の外に負ける */
@layer base { #t11 { color: red; } }  .a11 { color: blue; }
/* 10: LVHA の順序 */
a.lv1:hover { color: red; }  a.lv1:link { color: blue; }
a.lv2:link { color: blue; }  a.lv2:hover { color: red; }
<p id="t1" class="a">t1</p>  <p id="t2" class="x">t2</p>  <p id="t2b" class="x">t2b</p>
<p id="t3" class="y t3">t3</p>  <p id="t4" style="color: red;">t4</p>
<p id="t5" class="t5" style="color: red;">t5</p>
<article class="post"><p id="t7">t7</p></article>  <div class="w8"><p id="t8">t8</p></div>
<p id="t10" class="b10 a10">t10</p>  <p id="t11" class="a11">t11</p>
<a class="lv1" href="https://example.com/1">lv1</a>  <a class="lv2" href="https://example.com/2">lv2</a>
# 要素 競合したルール 勝ったルール getComputedStyle().color
1 t1 #t1 1-0-0 / .a 0-1-0 / p 0-0-1 #t1 rgb(0, 0, 255)(blue)
2 t2b .x で一致しただけでも :is(#t2, .x) は 1-0-0 / p.x.x.x 0-3-1(後) :is() rgb(0, 0, 255)(blue)
3 t3 :where(#t3, .y) 0-0-0 / p.t3 0-1-1 p.t3 rgb(255, 0, 0)(red)
4 t4 #t4 1-0-0 / インライン インライン rgb(255, 0, 0)(red)
5 t5 インライン / p.t5 !important !important rgb(0, 0, 255)(blue)
6 t7 ネスト & p 1-0-1 / .post p 0-1-1(後) ネスト側 rgb(0, 0, 255)(blue)
7 t8 .w8 p:not(#never) 1-1-1 / #t8 1-0-0(後) :not() 側 rgb(0, 0, 255)(blue)
8 t10 .a10 / .b10(同じ 0-1-0、.b10 が後) 後に書いた .b10 rgb(255, 0, 0)(red)
9 t11 @layer base { #t11 } / 層の外の .a11 層の外 rgb(0, 0, 255)(blue)
10 lv1 :hover を先、:link を後に書いた hover 中も :link 通常 blue → hover 中も rgb(0, 0, 255)
10 lv2 :link を先、:hover を後に書いた hover 中は :hover 通常 blue → hover 中 rgb(255, 0, 0)

特に注意したいのが t2b です。:is(#t2, .x) は .x 側で一致した要素にも「引数の中で最も高い 1-0-0」が付くため、後から書いた p.x.x.x(0-3-1)でも上書きできません。 詳細度を上げたくないグループ化には :where() を使います。


ネスト(&)の書き方と展開のされ方

CSS ネストは、Sass のように親セレクタの中に子のルールを書ける標準機能です(Chrome 120・Firefox 117・Safari 17.2 以降)。 & は「親セレクタそのもの」を表し、省略すると子孫結合子として扱われます。

.card {
  color: rgb(10, 10, 10);
  .title { color: rgb(1, 1, 1); }        /* → .card .title(& 省略 = 子孫) */
  &.active { color: rgb(2, 2, 2); }      /* → .card.active(& と密着させる) */
  .dark & { color: rgb(3, 3, 3); }       /* → .dark .card(& を後ろに置ける) */
  & + & { color: rgb(4, 4, 4); }         /* → .card + .card */
  p { color: rgb(5, 5, 5); }             /* → .card p(型セレクタ始まりも可) */
  &:hover { color: rgb(6, 6, 6); }       /* → .card:hover */
  @media (min-width: 1px) { color: rgb(7, 7, 7); }  /* @media もネストできる */
}

Chromium 141 の CSSOM が保持していたネスト内の selectorText は ["& .title", "&.active", ".dark &", "& + &", "& p", "&:hover", "@media (min-width: 1px)"] で、& を省略した .title と p には自動で & が補われている ことが分かります。

要素 状況 color
1つ目の .card &.active でも & + & でもない rgb(7, 7, 7)(@media 内の宣言が後にあるので勝つ)
2つ目の .card.active &.active(0-2-0)と & + &(0-2-0)の両方に一致 rgb(4, 4, 4)(同じ詳細度なので後に書いた & + &)
.dark の中の .card / .title / p .dark & / & .title / & p rgb(3, 3, 3) / rgb(1, 1, 1) / rgb(5, 5, 5)
1つ目の .card を hover &:hover rgb(6, 6, 6)

ネストの詳細度は :is() と同じ規則で、親がセレクタリストなら「その中で最も高いもの」になります(前の表の t7 で実測:#nothing, article { & p } は 1-0-1)。意図せず詳細度が上がるのを避けたいときは、親側を :where() で包むか、ID をリストに混ぜないようにします。


よくあるつまずきと直し方

つまずき 起きること 直し方
子孫 A B と子 A > B を混同 article p は .box の中の p も拾う(実測 4 件、article > p は 3 件) 直下だけなら > を使う
:nth-child() と :nth-of-type() を混同 p:nth-child(2) は「2番目の子が p のとき」だけ一致。間に h2 や div が挟まると当たらない 同じ型で数えるなら :nth-of-type()、条件つきなら :nth-child(n of S)
a:hover を a:link より先に書く 同じ詳細度なので後の :link が勝ち、hover しても色が変わらない(実測 lv1) L-V-H-A の順(:link → :visited → :hover → :active)で書く
擬似要素を単一コロンで書く :before / :after は互換のため動くが、input:placeholder や li:marker は SyntaxError 擬似要素は常に 二重コロン ::、擬似クラスは単一コロン
:is() で詳細度が上がる :is(#id, .x) は .x で一致しても 1-0-0(実測 t2b) 詳細度を足したくないときは :where()
:not() に無効なセレクタを混ぜる :not(.a, :nope) はルールごと無効(SyntaxError)。:is() / :where() は無効な引数だけ無視する 対応が不確かなセレクタは :is() に入れるか @supports selector() で分岐
:has() の入れ子・擬似要素 :has(:has(a)) と :has(::before) は SyntaxError :has() は1段だけ。擬似要素は外側に付ける
擬似要素の後ろに擬似クラス .a::before:hover は SyntaxError .a:hover::before の順に書く
||(列結合子)や [attr="v" s] を使う Chromium 141 では SyntaxError 列は td:nth-child(n) で指定し、大文字小文字は既定(区別あり)を使う
:visited で幅や content を変える プライバシー保護のため色系以外は反映されず、querySelectorAll(':visited') も 0 件 訪問済みは色の変更だけに留める

実務パターン集(コピペ用・Chromium 141 で適用値を確認)

/* P1 フォームのエラー表示(ユーザーが触った後だけ) */
.field input:user-invalid { border-color: rgb(220, 38, 38); }
.field .error { display: none; }
.field input:user-invalid + .error { display: block; }

/* P2 テーブルの縞 */
tbody tr:nth-child(even) { background-color: rgb(243, 244, 246); }

/* P3 最後の要素だけ余白なし */
.stack > * { margin-bottom: 16px; }
.stack > :last-child { margin-bottom: 0; }

/* P4 外部リンクに印(自サイトのドメインは除外) */
a[href^="http"]:not([href*="example.com"])::after { content: " ↗"; }

/* P5 必須項目のラベルに印(:has の1例) */
label:has(+ input:required)::after { content: " *"; color: rgb(220, 38, 38); }

/* P6 キーボード操作のときだけフォーカスリング */
button:focus-visible { outline: 3px solid rgb(37, 99, 235); }
button:focus:not(:focus-visible) { outline: none; }

/* P7 チェックで隣のラベルを強調 */
input[type="checkbox"]:checked + label { font-weight: 700; }

/* P8 先頭3つだけ強調 / 完了タスクの中で2番目だけ */
.list > li:nth-child(-n + 3) { font-weight: 700; }
.tasks > li:nth-child(2 of .done) { text-decoration-line: line-through; }

/* P9 モーダルの背面・行頭記号 */
dialog::backdrop { background-color: rgba(0, 0, 0, 0.5); }
li::marker { color: rgb(37, 99, 235); }
# パターン 確認した getComputedStyle の値
P1 エラー表示 操作前:border-color: rgb(118, 118, 118) / .error は display: none → 入力して消しフォーカスを外した後:rgb(220, 38, 38) / block
P2 テーブルの縞 1〜4行目の background-color:rgba(0, 0, 0, 0) / rgb(243, 244, 246) / rgba(0, 0, 0, 0) / rgb(243, 244, 246)
P3 最後だけ余白なし margin-bottom:16px / 16px / 0px
P4 外部リンクの印 外部:content: " ↗" / 自サイト:none
P4 必須ラベルの印 label::after の content: " *"
P5 フォーカスリング マウスクリック後:outline-style: none → Tab で戻った後:solid 3px rgb(37, 99, 235)
P6 チェックで強調 font-weight:400 → 700
P7 先頭3つ / .done の2番目 font-weight:700 / 700 / 700 / 400。text-decoration-line:4件中 4番目(.done の2つ目)だけ line-through
P9 背面・行頭記号 li::marker の color: rgb(37, 99, 235)(::backdrop は dialog 記事で検証)

どれを選ぶかの判断表

迷う場面 選ぶもの 理由
グループ化したい :is() 短く書ける。詳細度は引数の最高値になる点だけ意識する
リセットCSS・上書きされやすい基礎スタイル :where() 詳細度 0 なので、あとから素直に上書きできる
フォームのエラー表示 :user-invalid 触る前から赤くならない。:invalid は読み込み直後から一致する
フォーカスの見た目 :focus-visible マウスクリックではリングを出さず、キーボード操作では出す(実測どおり)
「n 番目」を数えたい 兄弟全体なら :nth-child()、同じ型なら :nth-of-type()、条件つきなら :nth-child(n of S) 数える母集団が違う
親や前の要素の見た目を変えたい :has() 2026年6月から Baseline Widely available。詳細度は引数の最高値
詳細度の競合を減らしたい クラス中心にし、ID セレクタと !important は最小限に 上書きの連鎖を防げる

よくある質問

Q. :is() と :where() はどちらを使えばよいですか? A. 一致する要素は同じで、違いは詳細度だけです。通常は :is() で問題ありませんが、リセットCSSやライブラリのように「あとで上書きされてほしい」スタイルは :where() にすると、利用側が詳細度を上げずに済みます。

Q. :nth-child(2) が当たらないのはなぜですか? A. :nth-child() は「同じ親の子要素すべて」の中で数えます。h2 の次に p があると p:nth-child(2) は一致しますが、その前に div などが挟まると当たりません。同じ型の中で数えるなら :nth-of-type(2)、特定のクラスだけ数えるなら :nth-child(2 of .done) を使います。

Q. ネストはもう本番で使えますか? A. Chrome 120・Firefox 117・Safari 17.2 以降で対応し、2026年6月に Baseline Widely available になりました。それより前の環境も対象にする場合は、Sass や PostCSS でネストを展開して配信すると安心です。


まとめ

  • セレクタは「基本・結合子・属性・擬似クラス・擬似要素・ネスト」の6グループ で整理すると覚えやすく、それぞれの詳細度と対応状況を早見表にまとめました。
  • 子孫 A B は 4 件、子 A > B は 3 件 のように、同じ HTML でも結合子で一致数が変わることを Chromium 141 で確認しました。
  • :focus-visible はマウスクリックしたボタンでは一致せず、キーボード移動やテキスト入力欄では一致 します。
  • :user-invalid は操作後または送信を試みた後だけ一致 し、:invalid は読み込み直後から一致します。
  • :is() とネストの & は「引数(親リスト)の中で最も高い詳細度」 が付き、:where() は常に 0-0-0 です。
  • 詳細度は インライン > ID > クラス・属性・擬似クラス > 型・擬似要素 で、!important はさらに上、@layer の中は層の外に負けます。
  • || と [attr="v" s] は Chromium 141 で SyntaxError、:visited は querySelectorAll で常に 0 件です。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「CSSセレクタ一覧|:has()・ネスト対応の早見表と詳細度」

株式会社RJCでは、CSSセレクタの一致数や詳細度の勝敗を実際に動かして確かめ、その結果をプロジェクトの中で仲間と共有しながら日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?