ようやく全ての主要ブラウザで使用可能になるCSSの擬似クラス:hasを使ってみよう

とても強力で便利な機能にも関わらず、Firefoxが未対応のため積極的に使用することができなかったCSSの擬似クラス:hasですが、ようやくFirefoxでの実装の目処がつきました。


:has() CSS relational pseudo-class | Can I use... Support tables for HTML5, CSS3, etc

Nightly版ではすでに使用可能となっているため、おそらく数ヶ月以内には通常版でも使用可能になるはず。

Firefox Nightly 121.0a1, See All New Features, Updates and Fixes (mozilla.org)

ようやく全ての主要ブラウザで使用可能になる:has疑似クラスについて、このブログではサンプルコードを通して具体的な使用例をいくつか紹介したいと思います。

CSSの擬似クラス:hasとは

CSSの疑似クラス:hasは、特定の子孫要素や隣接する兄弟要素を持つ親要素を選択するための強力なセレクタです。従来のCSSセレクタでは、主に要素自体の特性や属性に基づいてスタイルを適用していましたが、:has疑似クラスはこの限界を超え、要素のコンテキストや周囲のDOM構造に基づいた条件でスタイリングを可能にします。

この疑似クラスは「親セレクタ」とも呼ばれ、Webデザインにおいて非常に重要な役割を果たします。例えば、特定の子要素を含むリスト、特定の状態のフォーム要素を持つフォーム、または特定の内容を含むセクションなど、より複雑な条件に基づいて親要素にスタイルを適用することができます。

特別なアイテムを含むリストのスタイリング

このサンプルでは、.special-itemクラスを持つリストアイテムを含むul要素を強調表示します。
以下のように、:hasを使用して、.special-itemを含むul要素に境界線とパディングを追加し、リストアイテムのフォントを太字にします。

<section className="sample1">
  <ul>
    <li>アイテム 1</li>
    <li className="special-item">特別なアイテム</li>
    <li>アイテム 3</li>
  </ul>
  <ul>
    <li>アイテム 4</li>
    <li>アイテム 5</li>
  </ul>
</section>
ul:has(.special-item) {
  border: 2px solid blue;
  padding: 10px;
}

ul:has(.special-item) > li {
  font-weight: bold;
}

フォーム入力の検証結果に基づくラベルのカスタマイズ

このサンプルでは、フォーム入力が有効か無効かに基づいて、ラベルにチェックマークまたはバツマークを表示します。:has疑似クラスを使って、隣接する入力フィールドの検証結果に応じたスタイルをラベルに適用します。

<section className="sample2">
  <div className="form">
    <label htmlFor="name">名前</label>
    <input type="text" name='name' required />
  </div>
  <div className="form">
    <label htmlFor="email">メールアドレス</label>
    <input type="email" name='email' required />
  </div>
</section>
label:has( + input:valid)::before {
  content: '✓';
  font-size: 20px;
  color: blue;
}

label:has( + input:invalid)::before {
  content: '✕';
  font-size: 20px;
  color: red;
}

画像の有無に基づく図形のスタイリング

このサンプルでは、:not(:has(img))疑似クラスを使って、画像が含まれていないfigure要素に特別なスタイルを適用します。これにより、「No Image」というテキストを表示し、視覚的に区別します。

<section className="sample3">
  <figure></figure>
  <figure><img src={logo} alt="react" /></figure>
</section>
figure {
  position: relative;

  &:not(:has(img))::before {
    border: 1px solid black;
    content: 'No Image';
    display: grid;
    place-items: center;
    font-size: 16px;
    height: 100px;
    width: 100px;
  }
}

スクロール可能なリストのスタイリング

このサンプルでは、:has疑似クラスを用いて、スクロール方向に基づいてスクローラーに適切なオーバーフロースタイルを適用します。垂直方向のスクロールが可能なリストと水平方向のスクロールが可能なリストに対して、異なるスタイルを設定します。

<section className="sample4">
  <div className="scroller">
    <ul className="vertical">
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
      <li>5</li>
    </ul>
  </div>
  <div className="scroller">
    <ul className="horizontal">
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
      <li>5</li>
    </ul>
  </div>
</section>
.scroller {
  border: 1px solid black;
  width: 100px;
  height: 100px;

  &:has(.vertical) {
    overflow-y: auto;
    overflow-x: hidden;
  }

  &:has(.horizontal) {
    overflow-x: auto;
    overflow-y: hidden;
  }
}

ul {
  display: flex;
  gap: 10px;

  &.vertical {
    flex-direction: column;
  }

  &.horizontal {
    flex-direction: row;
  }

  & li {
    display: grid;
    flex: none;
    font-size: 100px;
    place-items: center;
    height: 100px;
    width: 100px;
  }
}

ツールチップの表示

このサンプルでは、ユーザーがツールチップトリガーにホバーしたときに、関連するデータテキストを表示します。:hasを使用して、ホバーされた要素の親リストアイテムにスタイルを適用し、カスタムのツールチップを表示します。

<section className="sample5">
  <ul>
   <li data-text="詳細情報1 項目1の詳細情報です">項目 1 <a href="#" className="tooltip-trigger">詳細</a></li>
  <li data-text="詳細情報2 項目2の詳細情報です">項目 2 <a href="#" className="tooltip-trigger">詳細</a></li>
  <li>項目 3 <a href="#" className="tooltip-trigger">詳細</a></li>
  </ul>
</section>
ul {
  position: relative;

  & .tooltip-trigger:hover {
    text-decoration: none;
  }

  & li:has(.tooltip-trigger:hover):is([data-text])::after {
    content: attr(data-text);
    position: absolute;
    background-color: black;
    color: white;
    padding: 5px;
    border-radius: 5px;
    top: 100%;
    left: 0;
  }
}

まとめ

いかがでしたでしょうか。
:has疑似クラスは、DOMの構造に基づいてスタイリングを適用する際に非常に便利です。
今まではJSを使って実装していたUIがCSSだけで実装可能になったりもします。
ようやく全ブラウザで使用可能になる:has疑似クラス、皆さんもぜひ使ってみてくださいね。