CSS セレクタの使い分けとは何か
CSS セレクタは、HTML の中から「どの要素を対象にするか」を表す記法で、querySelector / querySelectorAll でもそのまま使えます。id、class、タグ名、属性、階層関係、擬似クラスなどを組み合わせて、狙った要素だけを効率よく取得できます。ここが重要です:セレクタは「短く・安定・意図が明確」が正義。見た目のためのクラスと、JavaScript のフック(id や data-*)を分けると、壊れにくく保守しやすい紐づけになります。
基本セレクタの使い分け(id・class・タグ・属性)
id セレクタ(#id)
一意の要素を最短で取得。ページ内で重複しないことが前提です。
<h1 id="title">こんにちは</h1>
<script>
const el = document.querySelector("#title");
</script>
HTMLここが重要です:一意なら id を優先。読みやすく、性能も良いです。
クラスセレクタ(.class)
複数要素のグループを取得。最初の1件なら querySelector、全部なら querySelectorAll。
<p class="msg">A</p><p class="msg">B</p>
<script>
const first = document.querySelector(".msg");
const all = document.querySelectorAll(".msg");
</script>
HTMLここが重要です:見た目用のクラスと、JS 用のクラス(または data-*)は分けると変更に強くなります。
タグセレクタ(div, p など)
広く拾うときに便利。ただし対象が多くなりやすいので、スコープを絞るのが前提。
const firstDiv = document.querySelector("div");
JavaScriptここが重要です:タグ単体は曖昧になりがち。親要素からの検索で範囲限定をセットで考えましょう。
属性セレクタ([name=”…”] / [data-…])
data-* は JS フックのベストプラクティス。意味のあるメタ情報を持たせやすいです。
<button class="btn" data-role="buy">購入</button>
<script>
const buy = document.querySelector('.btn[data-role="buy"]');
</script>
HTMLここが重要です:data-* は「構造や見た目に依存しないフック」。将来の変更に強いです。
階層・関係セレクタ(親子・兄弟で絞る)
子孫(スペース)と直下の子(>)
スペースは「配下すべて」、> は「直下の子」だけ。
<div class="card"><h2 class="title">T</h2></div>
<script>
const anyDepth = document.querySelector(".card .title"); // 子孫
const direct = document.querySelector(".card > .title"); // 直下の子
</script>
HTMLここが重要です:DOM の入れ子が変わる可能性があるなら子孫、厳密に直下だけなら >。要件に合わせて使い分けます。
兄弟(+ と ~)
- は直後の兄弟、~ は後続の兄弟すべて。
<label class="switch"></label><input class="field">
<script>
const nextField = document.querySelector(".switch + .field"); // 直後の兄弟
</script>
HTMLここが重要です:兄弟セレクタは「並び」に依存します。並び変更に弱いので、優先度は低めに。
状態に応じた擬似クラス(:hover, :focus, :checked, :nth-child)
フォーム状態の取得と制御
選択状態やインデックスで絞り込みができます。DOM 取得では「現時点の条件に合うもの」を取れます。
<form id="prefs">
<input type="checkbox" class="opt" checked>
<input type="checkbox" class="opt">
</form>
<script>
const checked = document.querySelectorAll("#prefs .opt:checked");
const third = document.querySelector("#prefs .opt:nth-child(3)");
</script>
HTMLここが重要です:擬似クラスは強力ですが、動的に変わる条件です。必要ならイベントで再取得し直す設計にします。
セレクタ設計の深掘り(特異性・安定性・短さ)
特異性(specificity)を意識する
id > クラス/属性 > タグ の順に強くなります。JS の取得では「強すぎるセレクタ」は不要です。短く明確に。
// 悪い例:強くて長いセレクタ(変更に弱い)
document.querySelector("main .container .card .header .title");
// 良い例:安定したフック中心(短く、意味が明確)
document.querySelector('[data-role="card-title"]');
JavaScriptここが重要です:DOM 構造に依存する長いセレクタは壊れやすい。data-* や id を使って短く、目的に直結させます。
スコープで衝突回避
親要素から検索すると、同名クラスが複数あっても混ざりません。
const panel = document.getElementById("panel");
const btn = panel.querySelector(".btn");
JavaScriptここが重要です:コンポーネント単位のスコープ設計は、命名の衝突を自然に回避します。
特殊文字のエスケープ
id/class にドットやコロンが入っているときは、バックスラッシュでエスケープします。
<div id="user.name"></div>
<script>
const el = document.querySelector("#user\\.name");
</script>
HTMLここが重要です:エスケープが必要な命名は避けるのが理想。フックはシンプルに。
実践パターン(定番の取り方と落とし穴回避)
コンポーネント内のフック
構造に依存せず、役割ベースで安定取得。
<article class="card" id="product">
<h2 data-role="title">Title</h2>
<button data-role="buy">購入</button>
</article>
<script defer>
const card = document.querySelector("#product");
const title = card.querySelector('[data-role="title"]');
const buy = card.querySelector('[data-role="buy"]');
</script>
HTMLここが重要です:data-role のような役割名は、JS から読みやすく、構造変更にも強い。
複数取得と一括処理
静的 NodeList を前提に安定処理。
<div class="product-card"><button class="buy">Buy</button></div>
<div class="product-card"><button class="buy">Buy</button></div>
<script defer>
const cards = document.querySelectorAll(".product-card");
cards.forEach(card => {
const btn = card.querySelector(".buy");
btn.addEventListener("click", () => card.classList.add("purchased"));
});
</script>
HTMLここが重要です:親カードから子を取るスコープ設計で、意図しない混入を防ぎます。
null ガードとタイミング
DOM 未構築で null にならないように、defer か DOMContentLoaded で。
<script>
document.addEventListener("DOMContentLoaded", () => {
const el = document.querySelector("#title");
if (!el) return;
el.textContent = "ようこそ";
});
</script>
HTMLここが重要です:null は珍しくありません。毎回ガードを癖にすると、初期化が堅牢になります。
まとめ
CSS セレクタは、DOM 取得の「表現力」を支える土台です。id は最短で一意、class はグループ、data-* は安定フック、階層・兄弟で必要範囲に絞り、擬似クラスで状態に応じて選べます。セレクタは短く、構造依存を避け、スコープで衝突を回避し、data-* を活用して「役割」で紐づける。null ガードと適切なタイミングを守れば、初心者でも複雑な選択を安全に、読みやすく書けるようになります。
