CSS講座 / 結合子と擬似クラス:親子・兄弟・状態・順番

結合子はHTML要素の関係で対象を選び、擬似クラスは状態や構造上の条件で対象を選びます。子孫と直接の子は異なり、first-childは「同じ種類の最初」ではなく「親の最初の子要素」を意味します。

親・子・子孫・兄弟を文書構造で読む

次のsectionの直接の子はh2、段落A、div、段落Cです。Bはdivの子で、sectionから見ると子孫です。AとCは同じ親を持つ兄弟ですが、AとBは兄弟ではありません。画面上の近さではなく、この入れ子で判断します。

<section class="garden">
  <h2>Visitor Information</h2>
  <p>A: Operating hours</p>
  <div>
    <p>B: Greenhouse Guide</p>
  </div>
  <p>C: What you need</p>
</section>
section.garden
├─ h2
├─ p  ← A
├─ div
│  └─ p  ← B
└─ p  ← C

.garden pはA・B・C、.garden > pはA・C、h2 + pはA、h2 ~ pはA・Cを選びます。通常、装飾されるのは結合子の右側の条件に当たる要素です。改行や字下げの空白テキストは、この要素同士の兄弟関係を遮りません。基本セレクターと組み合わせて読みます。

子孫と直接の子を比較する

.garden p { color: #087f5b; }
.garden > p { border-left: 4px solid #087f5b; padding-left: 12px; }

最初の規則はgarden内のpすべてを緑にするため、途中にdivがあるBも含みます。二つ目は直接の子A・Cだけに左枠と余白を加えます。文字色と枠は異なるプロパティなので、A・Cには両方が適用されます。

区画内の段落を共通化するなら子孫、入れ子のカード内を除きたいなら直接の子が役立ちます。ただし、不要な子結合子を何段もつなぐとHTMLの深さに依存します。構造変更で一致が変わることを考えて選びます。MDNの結合子の説明も参照できます。

隣接する兄弟と後続の兄弟を比較する

h2 + p { background-color: #fff4cc; }
h2 ~ p { font-style: italic; }

最初の規則はh2の直後のpであるAの背景を黄色にし、二つ目は同じ親でh2の後にあるA・Cを斜体にします。Bは親が異なるので一致しません。どちらも前にある兄弟を選ぶ指定ではありません。

h2とAの間に<div>Additional notice</div>を入れると、Aは直後でなくなり黄色い背景が消えます。しかし後続の兄弟ではあるため斜体は残ります。順番を変えて、+と~の違いを結果から確かめます。

状態と順番を擬似クラスで選ぶ

a:hover { background-color: #fff4cc; }
a:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }
.plants li:first-child { font-weight: bold; }
.plants li:nth-child(odd) { background-color: #e9f7ef; }

:hoverはポインターを重ねた状態です。:focusはフォーカス自体、:focus-visibleはブラウザーがフォーカス表示を必要と判断する状態を選びます。この例は既定の表示を消さず追加の輪郭を付けています。マウスを重ねると黄色、Tabで移動すると青い輪郭になるか確認します。

タッチ環境でhoverを体験できるとは限らないため、営業時間やリンク名など必須情報は通常状態でも読めるようにします。実際のリンクはリンク状態とキーボードフォーカスで扱います。

:first-childは親の最初の子要素、:nth-child(odd)は子要素の1・3・5番目などに一致します。liだけのリストなら最初の項目が太字、1・3番目が薄い背景になります。evenにすると2・4番目です。

sectionの最初の子がh2なので、.garden > p:first-childはどの段落にも一致しません。nth-childも同じタグだけでなく全兄弟要素を数えます。種類別の位置を選ぶときはnth-of-typeなどを検討します。MDNの擬似クラスリファレンスで条件の違いを確認できます。

完全な例の表示を確認する

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Small Garden</title>
  <style>
body { margin: 24px; font-family: sans-serif; line-height: 1.7; }
    .garden p { color: #087f5b; }
    .garden > p { border-left: 4px solid #087f5b; padding-left: 12px; }
    h2 + p { background-color: #fff4cc; }
    h2 ~ p { font-style: italic; }
    .plants li:first-child { font-weight: bold; }
    .plants li:nth-child(odd) { background-color: #e9f7ef; }
    a:hover { background-color: #fff4cc; }
    a:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }
  </style>
</head>
<body>
<h1>Small Garden</h1>
  <section class="garden">
    <h2>Visitor Information</h2>
    <p>A: We open at 9am every day.</p>
    <div>
      <p>B: Please observe quietly in the greenhouse.</p>
    </div>
    <p>C: Bring a water bottle and comfortable shoes.</p>
  </section>
  <ul class="plants">
    <li>1: Mint</li>
    <li>2: Basil</li>
    <li>3: Rosemary</li>
    <li>4: Time</li>
  </ul>
  <a href="#contact">Go to the contact section</a>
  <h2 id="contact">Contact</h2>
  <p>Ask at the information desk.</p>
</body>
</html>

全体をindex.htmlとして保存して開くと、A・B・Cは緑、A・Cには左枠が付きます。Aは黄色い背景で、A・Cは斜体です。植物リストの最初の項目は太字、奇数の項目は薄い背景になります。A〜Cは表示上の比較用文字です。リンクはマウスとキーボードの両方で試します。

構造を変えて条件を確かめる

  1. h2とAの間にdivを一つ入れ、Aの背景だけが消え、斜体と左枠は残ることを確認してdivを削除します。
  2. Bを囲むdivタグだけを外してsectionの直接の子にします。Bにも左枠と斜体が付く一方、+の対象はAのままです。確認したらdivを戻します。
  3. :nth-child(odd)をevenに変え、背景が2・4番目へ移ることとfirst-childの太字が1番目に残ることを確認します。
  4. ポインターを離してページ内をクリックし、Tabでリンクへ移動します。輪郭が見えることとEnterで案内先へ移れることを確認します。アドレスバーにフォーカスがある場合はページへ戻してから試します。

一つの条件ごとに保存・確認・復元を行います。対象の範囲を変える問題は色の値だけでは直らず、キーボード操作の問題をhoverだけで解決することもできません。開発者ツールの状態固定も補助になりますが、最後は実際の操作で確かめます。