朗報! CSSの:has()疑似クラスがすべてのブラウザにサポートされました、:has()疑似クラスの便利な使い方のまとめ
Post on:2023年12月21日
CSSの:has()
疑似クラスは便利そうだけどブラウザのサポートがまだ、と見送っていた人に朗報です。12/19にリリースされたFirefox 121(リリース情報)でサポートされ、これで:has()
疑似クラスがすべてのブラウザにサポートされました。
そんな:has()
疑似クラスの便利な使い方を紹介します。
:has()疑似クラスのサポートブラウザ
Firefoxにサポートされたことで、:has()
疑似クラスがすべてのブラウザにサポートされました。
:has()疑似クラスのサポート状況
※まだFirefox 121の分がアップデートされていないようです。
Chrome, Edgeは105から、Safariは15.4からサポートされているので、来年は:has()
疑似クラスを使用する機会も増えるでしょう。
また、12/20にアップデートされたTailwind CSS v3.4(リリース情報)でも:has()
疑似クラスがサポートされました。
:has()疑似クラスの基礎知識
CSSの:has()
疑似クラスとは、指定した要素がある場合にのみスタイルを適用できるCSSの新機能です。
今までのCSSでは、要素の存在のあり・なしによって特定の親や要素にスタイルを設定することは不可能でした。あり用となし用のクラスを作成し、必要なバリエーションに応じて切り替える必要がありました。
たとえば、こんな感じです。
上: 画像があるカード、下: 画像がないカード
カードコンポーネントには、2つのバリエーションがあります。
- 画像あり
- 画像なし
今までのCSSでは、下記のように2つのクラスを作成して実装します。
1 2 3 4 5 6 7 8 9 10 11 12 |
/* 画像あり用のスタイル */ .card { display: flex; align-items: center; gap: 1rem; } /* 画像なし用のスタイル */ .card--plain { display: block; border-top: 3px solid #7c93e9; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!-- 画像ありのカード --> <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で配置できます。
1 2 3 4 |
.card:has(.card__image) { display: flex; align-items: center; } |
さらに詳しく知りたい人は、下記をご覧ください。
:has()疑似クラスの基本的な使い方
:has()
疑似クラスを使用すると、子の数に基づいて親要素のスタイルを設定できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* 最大3個の子(0を除く3以下) */ ul:has(> :nth-child(-n+3):last-child) { outline: 1px solid red; } /* 最大3個の子(0を含む3以下) */ ul:not(:has(> :nth-child(3))) { outline: 1px solid red; } /* ちょうど5個の子 */ ul:has(> :nth-child(5):last-child) { outline: 1px solid blue; } /* 10個以上の子 */ ul:has(> :nth-child(10)) { outline: 1px solid green; } /* 7~9個の子(境界を含む) */ ul:has(> :nth-child(7)):has(> :nth-child(-n+9):last-child) { outline: 1px solid yellow; } |
実際の動作は、デモページをご覧ください。
:has()
を使用して、子の数に基づいて親要素のスタイルを設定しています。
- 最大3個の子: レッドのアウトライン
- ちょうど5個の子: ブルーのアウトライン
- 10個以上の子: グリーンのアウトライン
- 7~9個の子: イエローのアウトライン
See the Pen
Styling parent elements based on the number of children with CSS :has() by coliss (@coliss)
on CodePen.
さらに詳しく知りたい人は、下記をご覧ください。
:has()疑似クラスの便利な使い方
:has()
疑似クラスを使用するとさまざまなセレクタを条件式のように記述できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/* figcaptionがあるfigure要素を選択 */ figure:has(> figcaption) { ... } /* svgの直接の子孫がないa要素を選択 */ a:not(:has(> svg)) { ... } /* inputの直接の兄弟があるlabel要素を選択 */ label:has(+ input) { … } /* 子孫のimgにaltがないarticle要素を選択 */ article:has(img:not([alt])) { … } /* DOM内で何らかの状態が存在するdocumentElementを選択 */ :root:has(.menu-toggle[aria-pressed=”true”]) { … } /* 子の数が奇数の.containerを選択 */ .container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... } /* ホバーされていないグリッド内のすべてのアイテムを選択 */ .grid:has(.grid__item:hover) .grid__item:not(:hover) { ... } /* カスタム要素<todo-list>を含むコンテナを選択 */ main:has(todo-list) { ... } /* 直接のhrの兄弟があるp要素内のすべてのa要素を選択 */ p:has(+ hr) a:only-child { … } /* 複数の条件を満たすarticle要素を選択 */ article:has(>h1):has(>h2) { … } /* h1の後にh2が続くarticle要素を選択 */ article:has(> h1 + h2) { … } /* インタラクティブな状態が発生したときに:rootを選択 */ :root:has(a:hover) { … } /* figcaptionがないfigureに続くp要素を選択 */ figure:not(:has(figcaption)) + p { … } |
では:has()
疑似クラスを実際にどのように使用するのか、いくつか例を見てみましょう。
カードの実装
まずはシンプルなカードのデモを見てみましょう。
カードにはタイトルやサブタイトル、画像などのメディアなど、あらゆる情報を配置できます。カードの基本的なHTMLは下記の通りです。
1 2 3 4 5 6 7 |
<li class="card"> <h2 class="card__title"> <a href="#">Some Awesome Article</a> </h2> <p class="card__blurb">Here's a description for this awesome article.</p> <small class="card__author">Chrome DevRel</small> </li> |
実際の動作は、デモをご覧ください。
See the Pen
Basic Card by coliss (@coliss)
on CodePen.
カードに画像を配置したいときはどうしますか?
カードでは2つのカラムを使用できます。今まではcard--with-media
やcard--two-columns
のようなクラスを作成していたかもしれません。このようなクラス名は、思いつくのが難しいだけでなく、それをずっと覚えておくのも難しいものです。
:has()
疑似クラスを使用すれば、カードが何らかのメディアを持っていることを検出して、適切なスタイルを適用できます。修飾子(Modifier)のクラス名は必要ありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<li class="card"> <h2 class="card__title"> <a href="/article.html">Some Awesome Article</a> </h2> <p class="card__blurb">Here's a description for this awesome article.</p> <small class="card__author">Chrome DevRel</small> <img class="card__media" alt="" width="400" height="400" src="./team-awesome.png" /> </li> |
実際の動作は、デモをご覧ください。
See the Pen
Card with Media by coliss (@coliss)
on CodePen.
これだけではありません、もっとクリエイティブになれるはずです。特集コンテンツを表示するカードは、レイアウト内でどのように適応させますか? 下記のCSSは、特集を表示するカードをレイアウトの幅いっぱいに表示し、グリッドの最初に配置します。
1 2 3 4 5 6 7 |
.card:has(.card__banner) { grid-row: 1; grid-column: 1 / -1; max-inline-size: 100%; grid-template-columns: 1fr 1fr; border-left-width: var(--size-4); } |
実際の動作は、デモをご覧ください。
See the Pen
:has Media Card Layouts by coliss (@coliss)
on CodePen.
さらに、バナーがある特集カードで注目を集めるためにアニメーションさせてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<li class="card"> <h2 class="card__title"> <a href="#">Some Awesome Article</a> </h2> <p class="card__blurb">Here's a description for this awesome article.</p> <small class="card__author">Chrome DevRel</small> <img class="card__media" alt="" width="400" height="400" src="./team-awesome.png" /> <div class="card__banner"></div> </li> |
アニメーションは6秒間隔で動かしてみます。
1 2 3 4 |
.card:has(.card__banner) { --color: var(--green-3-hsl); animation: wiggle 6s infinite; } |
実際の動作は、デモをご覧ください。
See the Pen
:has Wiggle Card by coliss (@coliss)
on CodePen.
:has()
疑似クラスには多くの可能性があります!
フォームの実装
フォームはCSSでスタイルするのが難しいことで知られています。たとえば、入力欄とそのラベルのスタイルです。入力が有効であることを示すにはどうすればよいでしょうか?
:has()
疑似クラスを使用すると、これが非常に簡単になります。:valid
や:invalid
など、関連するフォームの疑似クラスをフックにできます。
1 2 3 4 5 6 7 8 9 10 11 |
<div class="form-group"> <label for="email" class="form-label">Email</label> <input required type="email" id="email" class="form-input" title="Enter valid email address" placeholder="Enter valid email address" /> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
label { color: var(--color); } input { border: 4px solid var(--color); } .form-group:has(:invalid) { --color: var(--invalid); } .form-group:has(:focus) { --color: var(--focus); } .form-group:has(:valid) { --color: var(--valid); } .form-group:has(:placeholder-shown) { --color: var(--blur); } |
実際の動作は、デモをご覧ください。
有効な値と無効な値を入力し、フォーカスをオンまたはオフにしてみてください。
See the Pen
:has some <input> by coliss (@coliss)
on CodePen.
さらに、:has()
疑似クラスを使用して、エラーメッセージを表示・非表示することもできます。Emailのフィールドグループを使用して、エラーメッセージを追加してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<div class="form-group"> <label for="email" class="form-label"> Email </label> <div class="form-group__input"> <input required type="email" id="email" class="form-input" title="Enter valid email address" placeholder="Enter valid email address" /> <div class="form-group__error">Enter a valid email address</div> </div> </div> |
デフォルトでは、エラーメッセージを非表示にします。
1 2 3 |
.form-group__error { display: none; } |
そしてフィールドが:invalid
になり、かつフォーカスされていない場合、追加のクラス名を必要とせずにメッセージを表示できます。
1 2 3 |
.form-group:has(:invalid:not(:focus)) .form-group__error { display: block; } |
実際の動作は、デモをご覧ください。
See the Pen
This form shows and hides its error messages by coliss (@coliss)
on CodePen.
さらにフォームにセンスのいいアクションを加えてみます。無効な値を入力すると、フォームグループを揺らします(※ユーザーがモーション設定していない場合)。こういったマイクロインタラクションも:has()
疑似クラスを使用すると、簡単に実装できます。
See the Pen
This <input> :has an error message by coliss (@coliss)
on CodePen.
コンテンツの実装
コンテンツの実装は:has()疑似クラスの基本的な使い方で触れましたが、ドキュメントフローで:has()
疑似クラスを使用するにはどうすればよいでしょうか?
たとえば、テキスト間にある画像のスタイルです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
figure:not(:has(figcaption)) { float: left; margin: var(--size-fluid-2) var(--size-fluid-2) var(--size-fluid-2) 0; } figure:has(figcaption) { width: 100%; margin: var(--size-fluid-4) 0; } figure:has(figcaption) img { width: 100%; } |
この例では、画像がいくつ含まれています。画像にfigcaption
がない場合はコンテンツ内にフロートされ、figcaption
がある場合は全幅を占め、追加のマージンを与えます。
実際の動作は、デモをご覧ください。
See the Pen
An article that :has <figure> by coliss (@coliss)
on CodePen.
さらに詳しく知りたい人は、下記をご覧ください。
sponsors