HTML・CSS

CSSセレクターと条件指定:擬似クラス・擬似要素・詳細度を実例で理解する

この記事でわかること

CSSのセレクター、擬似クラス、擬似要素、@media・@supports・@container、カスケードと詳細度を実例で学びます。

CSSのセレクターは、スタイルを適用するHTML要素を選ぶ仕組みです。同じプロパティを書いても、セレクターが一致しなければ見た目は変わりません。この記事では選び方、状態による切り替え、画面や機能による条件指定、競合したルールの読み方を解説します。

基本のセレクター

要素・クラス・ID

HTML
<section id="lesson" class="panel">
  <h2>HTML入門</h2>
  <p class="lead">最初のページを作ります。</p>
  <a href="/html-css/">教材を読む</a>
</section>
CSS
p { line-height: 1.9; }          /* すべてのp */
.lead { font-size: 1.125rem; }   /* class="lead"を含む要素 */
#lesson { padding: 1.5rem; }    /* id="lesson"の要素 */
* { box-sizing: border-box; }  /* すべての要素 */
h2, h3 { line-height: 1.5; }    /* どちらの見出しも対象 */

同じクラスは複数の要素へ付けられます。idは文書内で一意にします。IDセレクターは詳細度が高いため、再利用する部品の装飾はクラスを中心にすると調整しやすくなります。

関係で選ぶ結合子

CSS
.panel p { color: #355041; }      /* 子孫のp */
.panel > h2 { margin-top: 0; }    /* 直接の子のh2 */
h2 + p { font-weight: 600; }      /* 直後の兄弟p */
h2 ~ p { max-width: 65ch; }       /* 同じ親を持つ後続のp */

空白は「どこかの子孫」、> は「直接の子」です。途中にdivを追加すると > の一致は変わります。HTML構造を深くたどるセレクターは、構造変更に弱くなることがあります。

属性で選ぶ

有無・完全一致・部分一致

CSS
[disabled] { opacity: 0.65; }
input[type="email"] { border-color: #326744; }
[data-level~="beginner"] { font-weight: 600; }
a[href^="https://"] { text-underline-offset: 0.2em; }
a[href$=".pdf"] { font-weight: 600; }
a[href*="guide"] { text-decoration-style: dotted; }
[lang|="en"] { font-family: Georgia, serif; }
  • [attr]:その属性が存在する要素。
  • [attr="value"]:値が完全に一致する要素。
  • [attr~="word"]:空白区切りの語に一致する要素。
  • [attr|="en"]:enそのものかen-で始まる値。
  • [attr^="prefix"]:指定した文字で始まる値。
  • [attr$="suffix"]:指定した文字で終わる値。
  • [attr*="part"]:指定した文字列を含む値。

.pdf の末尾一致は、URLの後ろにクエリーやフラグメントが付くと一致しません。取得先の実際のファイル形式を判定しているわけではない点に注意します。

擬似クラスで状態を選ぶ

操作とフォームの状態

  • :hover:ポインターが重なった状態。タッチ端末だけで使う操作をhoverへ依存させません。
  • :focus:フォーカスを持つ要素。
  • :focus-visible:ブラウザーがフォーカスを視覚的に示すべきと判断した状態。
  • :focus-within:自身か子孫にフォーカスがある状態。
  • :active:操作中の状態。
  • :checked:選択中のチェックボックスやラジオボタンなど。
  • :disabled:enabled:標準のフォーム部品などの無効・有効状態。
  • :required:optional:必須指定の有無。
  • :valid:invalid:標準の制約検証に合う・合わない状態。
  • :placeholder-shown:プレースホルダーが表示されている状態。
  • :target:URLのフラグメントが指す要素。
  • :link:visited:未訪問・訪問済みのリンク。visitedにはプライバシー上のスタイル制限があります。
CSS
button:focus-visible { outline: 3px solid #9b451d; outline-offset: 4px; }
.field:focus-within { background: #edf5e7; }
input:invalid { border-color: #a42a40; }

invalidの色だけでエラーを伝えず、エラーの理由も文章で示します。入力開始前から必須項目を真っ赤にする設計が適切かも検討します。aria-disabled属性は標準のdisabled属性と同じ操作抑止を自動では行いません。

並び順・条件を使う

CSS
li:first-child { margin-top: 0; }
li:last-child { margin-bottom: 0; }
li:nth-child(2n) { background: #f1f5ed; }
p:first-of-type { font-weight: 600; }
.card:not(.featured) { border-color: #c8d4c6; }
:is(h2, h3) { scroll-margin-top: 6rem; }
:where(.article) p { line-height: 1.9; }
.card:has(img) { padding-top: 0; }

:nth-child() は兄弟要素全体の順番、:nth-of-type() は同じ要素型の中での順番です。:not() は除外、:is() は候補のまとめ、:has() は条件に合う子孫や関係する要素を持つものを選びます。:has() を使う前は古い対象ブラウザーも確認します。

擬似要素で要素の一部を装飾する

前後の装飾と文字の一部

  • ::before::after:要素の前後に生成内容を置く。通常はcontentの指定が必要です。
  • ::first-letter:最初の文字。
  • ::first-line:最初の行。行は画面幅などにより変わります。
  • ::marker:リストの項目記号。
  • ::selection:利用者が選択した文字の範囲。
  • ::placeholder:入力欄のプレースホルダー文字。
CSS
.note::before { content: ""; display: inline-block; width: 0.5rem; height: 0.5rem; margin-right: 0.5rem; border-radius: 50%; background: #326744; }
li::marker { color: #326744; }
::selection { color: #15201f; background: #d2f777; }

大切な注意事項やボタン名をcontentだけで作らず、HTMLに書きます。擬似要素ごとに使えるプロパティや対応に制約があります。

@ルールで条件を切り替える

画面・機能・部品の幅

CSS
.cards { display: grid; grid-template-columns: 1fr; gap: 1rem; }
@media (min-width: 48rem) {
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@supports (display: grid) {
  .layout { display: grid; gap: 1rem; }
}
.card-container { container-type: inline-size; }
@container (min-width: 30rem) {
  .card { display: flex; gap: 1rem; }
}

@media は画面などの条件、@supports は宣言の対応、@container は祖先のクエリーコンテナーの条件です。コンテナークエリーでは、調べたい幅を持つ親にcontainer-typeを設定します。条件でコンテナー自身を無制限に変えられる仕組みではありません。

動き・印刷・フォント

CSS
@media print {
  .site-navigation { display: none; }
  body { color: #000; background: #fff; }
}
@keyframes enter {
  from { opacity: 0; transform: translateY(0.25rem); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: no-preference) {
  .notice { animation: enter 180ms ease-out; }
}

@keyframes は動きの途中の状態を定義し、animationで利用します。@font-face は独自フォントの読み込みに使います。ファイルの配信、ライセンス、表示を待つ挙動も考慮します。@import は別のCSSを読み込めますが、読み込みの連鎖が増えるため構成を整理します。

詳細度とカスケード

後に書けば必ず勝つわけではない

ブラウザーは宣言の由来、重要度、レイヤー、詳細度などを評価します。同じ条件・同じ詳細度なら後の宣言が優先されます。まず開発者ツールで何が競合しているかを見て、!importantを足す前に原因を調べます。

  • IDセレクターは、クラス・属性・擬似クラスより高い詳細度です。
  • クラス・属性・擬似クラスは、要素型・擬似要素より高い詳細度です。
  • 全称セレクターや結合子そのものは詳細度を増やしません。
  • :where() の詳細度は引数も含めて0です。
  • :is():not():has() は引数に含まれる最も高い詳細度が影響します。
  • 継承は、その要素自身に適用される宣言と同じ競争をするわけではありません。
CSS
@layer base, components;
@layer base {
  a { color: #285c40; }
}
@layer components {
  .button { color: #fff; background: #285c40; }
}

通常の宣言では後のレイヤーが優先されますが、レイヤー外の通常宣言や!importantの優先関係も別にあります。レイヤーだけで全競合を解決したと考えないでください。

活用課題と参考資料

教材のカードにfeaturedクラスを付け、背景を変えてみましょう。次に、画像のあるカードだけを:has()で選びます。対応しない環境でもカードの文章とリンクが使えるよう、通常の表示を先に作ってから追加の装飾を重ねます。

y.
WRITTEN BY

y_ymo10

SEの部屋で、HTML・CSSからJavaScript・React・Next.jsまで、Web制作の開発ノートを公開しています。

ほかの開発ノートを読む →