CSSの:has()疑似クラスの便利な使い方を徹底解説

job_illustrator_pc_woman-e ※Web開発
CSSの:has()疑似クラスの便利な使い方を徹底解説
先日の記事で:has()疑似クラスがSafariでサポートされ、こんなことができるというのを紹介しましたが、さらに:has()疑似クラスを掘り下げ、Webサイトやアプリでの便利な使い方を紹介します。

先日の記事:has()疑似クラスがSafariでサポートされ、こんなことができるというのを紹介しましたが、さらに:has()疑似クラスを掘り下げ、Webサイトやアプリでの便利な使い方を紹介します。

:has()疑似クラスは、指定した要素がある場合にのみスタイルを適用できるCSSの新機能で、これからのWeb制作に活躍するかなり便利な機能です。こういう機能を待ち望んでいた人も多いと思います。

たとえば、カードに画像がある場合、ナビゲーションに子メニューがある場合、ラッパーがある場合など、複雑なCSSが必要だったものやJavaScriptが必要だったものが簡単でシンプルなCSSで実装できます。

CSSの:has()疑似クラスの便利な使い方を徹底解説

CSS Parent Selector
by Ahmad Shadeed

下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。

はじめに

CSSで、特定の要素が親の中に存在するかどうかをチェックできたら便利だなと思ったことはありませんか? たとえば、カード内に画像がある(has)場合にはdisplay: flex;を追加する、という感じです。今までは不可能でしたが、CSSに新しいセレクタ:has()が登場し、特定の要素が親の中に存在するかなど、さまざまな要素を選択できるようになります。

この記事では、:has()で解決される問題、それがどのように機能するか、その実装方法とどこでどのように使用できるか、そして最も重要なこととして、今日から使用できる方法について解説します。

:has()を使用すると、こんなことができるようになります

今までのCSSでは、要素の存在のあり・なしによって特定の親や要素にスタイルを設定することは不可能でした。あり用となし用のクラスを作成し、必要なバリエーションに応じて切り替える必要がありました。

たとえば、こんな感じです。

カードのコンポーネント

上: 画像があるカード、下: 画像がないカード

カードコンポーネントには、2つのバリエーションがあります。

  1. 画像あり
  2. 画像なし

今までのCSSでは、下記のように2つのクラスを作成して実装します。

CSS

/* 画像あり用のスタイル */
.card {
    display: flex;
    align-items: center;
    gap: 1rem;
}
/* 画像なし用のスタイル */
.card--plain {
    display: block;
    border-top: 3px solid #7c93e9;
}

HTML

<!-- 画像ありのカード -->
<div class="card">
    <div class="card__image">
        <img src="awameh.jpg" alt="">
    </div>
    <div class="card__content">
        <!-- Card content here -->
    </div>
</div>
 
<!-- 画像なしのカード -->
<div class="card card--plain">
    <div class="card__content">
        <!-- Card content here -->
    </div>
</div>

画像なしにはFlexboxが必要ないため、なし用とあり用の2つのバリエーションのクラスを作成しました。このような場合にバリエーションのクラスを作成しないで、CSSで条件式のように定義できるとしたら、どう思いますか。

ここで、CSSの:has()の出番です!
:has()を使用すると、.card要素の中に.card__imageがあるかどうかをチェックできます。

たとえば、カードに画像があるかどうかをチェックし、ある場合にはFlexboxで配置できます。

CSS

	
.card:has(.card__image) {
    display: flex;
    align-items: center;
}

:has()疑似クラスとは

CSSの仕様によると、:has()疑似クラスは親要素に少なくとも1つの要素が含まれているかどうか、あるいはフォーカスが当たっているかどうかなど、1つの条件が含まれているかどうかをチェックできます。

前述のCSSをもう一度確認してみましょう。

CSS



.card:has(.card__image) { }

このCSSは、.cardの親要素に.card__imageの子要素が含まれているかどうかをチェックします。

:has()疑似クラスとは

このCSSを言葉に置き換えてみます。

画像が含まれているカードにのみ、スタイルを適用します。

これはすごいことだと思いませんか!
CSSでロジックを扱えるようになりました。CSSでこんなことができるとは、なんて素晴らしい時代になったのでしょう。

:has()疑似クラスは親だけではありません

:has()疑似クラスは、親が子を含んでいるかどうかだけでなく、たとえば、ある要素の後に<p>が続いているかどうかもチェックできます。

CSS

.card h2:has(+ p) { }

このCSSは、<h2>要素の直後に<p>要素が続くかどうかをチェックします。

また、form要素に使用してフォーカスされたinput要素があるかどうかをチェックする、といった使い方もできます。

CSS

/* フォーム内にフォーカスされた入力欄がある場合、スタイルを適用します。 */
form:has(input:focused) {
    background-color: lightgrey;
}

:has()疑似クラスのサポート状況

:has()疑似クラスは、先日リリースされたSafari 15.4でサポートされました。2022年4月現在、Safari 15.4とChrome Canaryでのみ動作します。Chromeは次期101のflagsで使用できる予定で、すべてのブラウザにサポートされる日が近づいてきました。

【アップデート】
2022/9: Chrome, Edge, Operaでもサポートされました。

サイトのキャプチャ

:has()疑似クラスのサポート状況

:has()疑似クラスはエンハンスメントとして使用できるか

はい、CSSの@supportsルールで使用できます。

CSS

@supports selector(:has(*)) {
    /* do something */
}

【訳者注】
:has()疑似クラスをすぐに使用したい場合は、ポリフィルもリリースされています。

:has()疑似クラスの使用例

:has()疑似クラスを使用した便利な使い方を紹介します。Webサイトやアプリでよく使用されるコンポーネントやレイアウトに大活躍します。

セクションのヘッダ

セクションのヘッダを実装する時、タイトルだけ、タイトルとリンク、の2つのバリエーションを用意することがあります。

セクションのヘッダ

デモページ

リンクの有無によって、スタイルを変えます。

HTML

<section>
    <div class="section-header">
        <h2>Latest articles</h2>
        <a href="/articles/>See all</a>
    </div>
</section>

:has(< a)で、直接の子リンクがあるかどうかをチェックします。

CSS

.section-header {
  display: flex;
  justify-content: space-between;
}
/* リンクがある場合のスタイル */
.section-header:has(> a) {
  align-items: center;
  border-bottom: 1px solid;
  padding-bottom: 0.5rem;
}

カードコンポーネント 1

最初のカードの例に少し話を戻しましょう。画像ありと画像なしの2つのバリエーションがあります。

カードコンポーネント

デモページ

まずは、前述のCSSです。画像ありの場合にスタイルを適用します。

CSS

.card:has(.card__image) {
    display: flex;
    align-items: center;
}

:not()を使用すると画像なしもチェックできます。たとえば、画像なしの場合は上にボーダーを適用します。

CSS

.card:not(:has(.card__image)) {
    border-top: 3px solid #7c93e9;
}

:has()疑似クラスを使用しないでこれを実装すると、下記のように2つのクラスが必要になります。

CSS

.card--default {
    display: flex;
    align-items: center;
}
.card--plain {
    border-top: 3px solid #7c93e9;
}

カードコンポーネント 2

カードの違う使用例を見てましょう。
このカードでは、下部にアクションがない場合とアクションがある場合の2つのバリエーションがあります。

カードコンポーネント

デモページ

カードのアクションに2つの異なるラッパーがある場合、下記のようにdisplay: flex;を有効にしたいと思います(HTMLは気にしないでください、単にデモ用です)。

HTML

<div class="card">
    <div class="card__thumb>
<img src="cool.jpg"/>
</div>
    <div class="card__content">
        <div class="card__actions">
            <div class="start">
                <a href="#">Like</a>
                <a href="#">Save</a>
            </div>
            <div class="end">
                <a href="#">More</a>
            </div>
        </div>
    </div>
</div>

CSS

1234.card__actions:has(.start, .end) {    display: flex;    justify-content: space-between;}

:has()疑似クラスを使用しない場合は、下記の通りです。

CSS

.card--with-actions .card__actions {
    display: flex;
    justify-content: space-between;
}

カードコンポーネント 3

カードに画像があるかないかでborder-radiusを変更する必要があったことはありませんか。:has()疑似クラスの完璧な使用例です。

単に画像を取り除いてしまうだけと、残された上部の角丸がなくなってしまい、奇妙に見えます。

カードコンポーネント

デモページ

CSS

/* 画像なしの場合、左上と右上に角丸を追加します。 */
.card:not(:has(img)) .card__content {
    border-top-left-radius: 12px;
    border-top-right-radius: 12px;
}
.card img {
    border-top-left-radius: 12px;
    border-top-right-radius: 12px;
}
.card__content {
    border-bottom-left-radius: 12px;
    border-bottom-right-radius: 12px;
}

:has()疑似クラスを使用すると、画像なしの場合の角丸も簡単に実装できます。

カードコンポーネント

デモページ

:has()疑似クラスを使用しない場合は、下記の通りです。

CSS

.card--plain .card__content {
    border-top-left-radius: 12px;
    border-top-right-radius: 12px;
}

フィルタリングコンポーネント

フィルタリングは、複数のオプションを持つコンポーネントです。いずれもチェックされていない場合は、リセットボタンはありません。そして、少なくとも1つがチェックされている場合は、リセットボタンを表示する必要があります。

フィルタリングコンポーネント

デモページ

:has()疑似クラスを使用すると、簡単に実装できます。

CSS

.btn-reset {
    display: none;
}
.multiselect:has(input:checked) .btn-reset {
    display: block;
}

これは:has()疑似クラスを使用しない場合は、CSSで実装はできません。:has()疑似クラスのサポートが良好になったら、JavaScriptを捨てる予定の一つです。

フォーム要素を条件付きで表示または非表示

以前の回答や選択に基づいて、フォームの項目を表示する場合があります。たとえば、ユーザーがセレクトメニューから「その他」を選択した場合に、「その他」フィールドを表示する必要があります。

フォーム要素を条件付きで表示または非表示

デモページ

:has()疑似クラスを使用すると、セレクトメニューで「その他」が選択されているかどうかをチェックし、それに基づいて「その他」フィールドを表示できます。

CSS

.other-field {
    display: block;
}
form:has(option[value="other"]:checked) .other-field {
    display: block;
}

これも素晴らしいことだと思いませんか?
セレクトメニューとフィールドが.box親要素内にあれば、HTMLソースの順番を気にする必要はありません。

サブメニュー付きのナビゲーション

ホバーやフォーカスで表示されるサブメニューを備えたナビゲーションがあります。

サブメニュー付きのナビゲーション

デモページ

ここでやりたいことは、サブメニューがあるかないかで矢印を表示・非表示にすることです。:has()疑似クラスを使用すれば、簡単にできます。<li><ul>が含まれているかどうかをチェックし、ある場合に矢印アイコンを表示します。

CSS

li:has(ul) > a:after {
    content: "";
    /* 矢印のスタイル */
}

:has()疑似クラスを使用しない場合は、おそらくサブメニューを備えた<li>へのクラスで実装します。

CSS

.nav-item--with-sub > a:after {
    content: "";
    /* 矢印のスタイル */
}

ヘッダのラッパー

ヘッダコンポーネントを実装する際、ヘッダをページの全幅にするか、ラッパー内に含めるか迷うことがあります。

ヘッダのラッパー

デモページ

どちらにせよ、ヘッダ内の要素を配置するためにFlexboxを使用する必要があります。.wrapperがある場合は、それにスタイルを適用します。そうでない場合は、.site-header要素に直接適用します。

HTML

<header class="site-header">
    <div class="wrapper">
        <!-- ヘッダ内の要素 -->
    </div>
</header>

CSS

.site-header:not(:has(.wrapper)) {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-inline: 1rem;
}
/* ラッパーがある場合 */
.site-header .wrapper {
    display: flex;
    justify-content: space-between;
    align-items: center;
    max-width: 1000px;
    margin-inline: auto;
    padding-inline: 1rem;
}

アラートの強調表示

ユーザーが知っておかなければならない重要なアラートが存在する場合があります。その場合、通常のアラートだけでは十分でない可能性があります。そのような場合、たとえば、header要素に赤いボーダーと薄赤色の背景色を追加して表示します。

アラートの強調表示

デモページ

:has()疑似クラスを使用すれば、.main要素にアラートがあるかどうかをチェックし、ある場合にはヘッダにスタイルを追加できます。

CSS

.main:has(.alert) .header {
    border-top: 2px solid red;
    background-color: #fff4f4;
}

カラーテーマの変更

:has()疑似クラスを使用して、Webページの配色を変更できます。たとえば、CSS変数を使用して作成された複数のテーマがある場合、セレクトメニューで変更できます。

CSS

html {
    --color-1: #9e7ec8;
    --color-2: #f1dceb;
}
カラーテーマの変更

デモページ

セレクトメニューからカラーを選択すると、CSSでは次のようなことが起こります。選択されたオプションに基づいて、CSSの変数が変更されます。

CSS

html:has(option[value="blueish"]:checked) {
    --color-1: #9e7ec8;
    --color-2: #f1dceb;
}
カラーテーマの変更

デモページ

生成されたHTMLのスタイル

場合によっては、HTMLを変更できないことがあります。たとえば、記事の本文内です。コンテンツ管理システム(CMS)が予期せぬ方法で要素を生成するかもしれませんし、著者が動画などを埋め込むかもしれません。

本文内で、段落が続いていない<h3>を選択し、その下のスペースを広げます。

CSS

.article-body h3:not(:has(+ p)) {
    margin-bottom: 1.5rem;
}

あるいは、<h3>に続いている<iframe>を選択して実行する必要があるかもしれません。このような場合は:has()疑似クラスでないと対応できません。

CSS

.article-body h3:has(+ p) {
    /* do something */
}

アイコン付きのボタン

デフォルトのボタンスタイルがあり、さらにアイコンがある場合があります。アイコン付きはFlexboxを使用して中央揃えにします。

アイコン付きのボタン

デモページ

:has()疑似クラスを使用して、アイコンがある場合のスタイルを設定します。

CSS

.button:has(.c-icon) {
    display: inline-flex;
    justify-content: center;
    align-items: center;
}

複数のボタン

デザインシステムにおいて、アクションボタンを複数個用意することはよくあります。2つ以上のボタンがある場合、最後のボタンは反対側の端に表示されます。

複数のボタン

デモページ

数量クエリを使用して実現できます。CSSでボタンの数が3以上かどうかをチェックし、3以上の場合はmargin-left: auto;で右に押し出すように配置します。
参考: CSSのプロパティの値に「auto」を使ったテクニックのまとめ、マージンやサイズや配置やFlexboxなど

CSS

.btn-group {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.btn-group:has(.button:nth-last-child(n + 3)) .button:last-child {
    margin-left: auto;
}

情報モジュール

この例は、Pinterestのデザインシステムから得たものです。入力にエラーがあるときは、見出しを変えてその旨を表示します。

情報モジュール

:has()疑似クラスを使用して、エラーがある場合のスタイルを設定します。

CSS

.module:has(.input-error) .headline {
    color: #ca3131;
}

グリッドをアイテム数に基づいて変更する

CSS Gridでは、minmax()関数を使用して、真にレスポンシブで自動サイズのグリッドアイテムを作成できます。しかし、これだけでは不十分な場合があります。また、アイテム数に応じてグリッドを変更したいと思います。
参考: しっかり理解しておくと便利なCSSのテクニック、minmax()関数の使い方

グリッドをアイテム数に基づいて変更する

CSS

.wrapper {
    --item-size: 200px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--item-size), 1fr));
    gap: 1rem;
}

アイテムが5つの場合、最後のアイテムは新しい行に折り返されます。

グリッドをアイテム数に基づいて変更する

.wrapperに5つ以上のアイテムが含まれているかどうかをチェックすることで、これを実装できます。これはさきほどの数量クエリを使用しています。

CSS

.wrapper:has(.item:nth-last-child(n + 5)) {
    --item-size: 120px;
}
グリッドをアイテム数に基づいて変更する

画像とキャプション

キャプションなしの画像とキャプションありの画像があります。

画像とキャプション

この例では、HTMLの<figure>で実装されています。<figcaption>があれば、スタイルは少し異なるはずです。

  • 白い背景を追加する
  • パディングを少し加える
  • 画像の角丸を少し小さくする

CSS

	
figure:has(figcaption) {
    padding: 0.5rem;
    background-color: #fff;
    box-shadow: 0 3px 10px 0 rgba(#000, 0.1);
    border-radius: 3px;
}

終わりに

みなさんが:has()疑似クラスを使用して何を実装するのか、今から楽しみです。この記事で紹介した使用例は、ほんの一部に過ぎません。この先、たくさんの便利な使用方法が見つかると確信しています。

今がCSSを学ぶのに一番いい時期だと思います。次に何が起こるか、とても楽しみです。ここまで、お読みいただきありがとうございました。

関連リソース

この記事があなたのお役に立てれば幸いです。
コメントや提案があれば、@shadeed9までお願いします。

タイトルとURLをコピーしました