古くから(CSS の用語で言えば)、さまざまな意味でカスケードを使用してきました。スタイルは「カスケーディング スタイル シート」を構成します。セレクタもカスケードします。横方向に移動することもあります。ほとんどの場合、下向きになります。ただし、上昇することは決してありません。長年、Google は「親セレクタ」の開発を夢見てきました。いよいよリリースされます。:has()
疑似セレクタの形で指定します。
:has()
CSS 疑似クラスは、パラメータとして渡されたセレクタのいずれかが少なくとも 1 つの要素と一致する場合に、その要素を表します。
ただし、これは単なる「親」セレクタではありません。良いマーケティング方法ですね。あまり魅力的ではない方法としては、「条件付き環境」セレクタがあります。でも、それはちょっと違う響きです。「family」セレクタはどうですか?
対応ブラウザ
先に進む前に、ブラウザのサポートについて説明します。まだ完了していません。ただし、近づいてきています。Firefox は現在サポートされていませんが、今後サポートされる予定です。ただし、この機能はすでに Safari で利用可能で、Chromium 105 でリリースされる予定です。この記事のデモはすべて、使用しているブラウザでサポートされていない場合はその旨を通知します。
:has の使用方法
具体的にはどのようなものなのか? クラス everybody
の 2 つの兄弟要素を含む次の HTML について考えてみましょう。クラス a-good-time
を持つ子孫を持つものをどのように選択しますか。
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
:has()
では、次の CSS を使用してこの操作を行うことができます。
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
これにより、.everybody
の最初のインスタンスが選択され、animation
が適用されます。
この例では、クラスが everybody
の要素がターゲットです。条件は、クラス a-good-time
を持つ子孫を持つことです。
<target>:has(<condition>) { <styles> }
ただし、:has()
を使用すると、さらに多くのことを実現できます。まだ発見されていない可能性のあるものも含みます。次のことを検討してください。
直接の figcaption
を持つ figure
要素を選択します。css
figure:has(> figcaption) { ... }
直接の SVG 子孫を持たない anchor
を選択します。
css
a:not(:has(> svg)) { ... }
直接の input
兄弟を持つ label
を選択します。横向きになりました。
css
label:has(+ input) { … }
子孫 img
に alt
テキストがない article
を選択
css
article:has(img:not([alt])) { … }
DOM に状態が存在する documentElement
を選択
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
子の数を奇数にするレイアウト コンテナを選択
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
ホバーされていないグリッド内のすべてのアイテムを選択
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
カスタム要素 <todo-list>
を含むコンテナを選択
css
main:has(todo-list) { ... }
直接的な兄弟 hr
要素を持つ段落内の単独の a
をすべて選択
css
p:has(+ hr) a:only-child { … }
複数の条件が満たされる article
を選択
css
article:has(>h1):has(>h2) { … }
組み合わせます。タイトルの後にサブタイトルが続く article
を選択します
css
article:has(> h1 + h2) { … }
インタラクティブな状態がトリガーされたときに :root
を選択します
css
:root:has(a:hover) { … }
figcaption
のない figure
の後の段落を選択します
css
figure:not(:has(figcaption)) + p { … }
:has()
の興味深いユースケースを思いつきましたか?興味深いのは、メンタルモデルを破壊することを奨励していることです。「これらのスタイルに別の方法でアプローチできないか?」と考えさせます。
例
では、この機能の使用例をいくつか見てみましょう。
カード
クラシック カードのデモを試す。カードには、タイトル、サブタイトル、メディアなど、任意の情報を表示できます。基本カードは次のとおりです。
<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>
メディアを導入したい場合はどうすればよいですか?このデザインでは、カードを 2 つの列に分割できます。以前は、この動作を表す新しいクラス(card--with-media
や card--two-columns
など)を作成していました。このようなクラス名は、思い浮かべにくくなるだけでなく、メンテナンスや記憶も難しくなります。
:has()
を使用すると、カードにメディアがあることを検出して適切な処理を行うことができます。修飾子クラス名は不要です。
<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>
そのままにしておく必要はありません。クリエイティブな使い方もできます。「注目の」コンテンツを表示するカードは、レイアウト内でどのように適応しますか?この CSS を使用すると、注目のカードがレイアウトの全幅になり、グリッドの先頭に配置されます。
.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);
}
バナー付きの注目のカードが注意を引くために動く場合
<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>
.card:has(.card__banner) {
--color: var(--green-3-hsl);
animation: wiggle 6s infinite;
}
可能性は無限大です。
フォーム
フォームはどうですか?スタイリングが難しいことで知られています。たとえば、入力とそのラベルのスタイル設定がこれに該当します。たとえば、フィールドが有効であることを通知するにはどうすればよいですか?:has()
を使用すると、この作業が大幅に簡素化されます。関連するフォームの疑似クラス(:valid
や :invalid
など)にフックできます。
<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>
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);
}
次の例で試してみてください。有効な値と無効な値を入力し、フォーカスをオンとオフにします。
:has()
を使用して、フィールドのエラー メッセージを表示または非表示にすることもできます。「email」フィールド グループを取得して、エラー メッセージを追加します。
<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>
デフォルトでは、エラー メッセージを非表示にします。
.form-group__error {
display: none;
}
ただし、フィールドが :invalid
になり、フォーカスされていない場合は、追加のクラス名を必要とせずにメッセージを表示できます。
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
ユーザーがフォームを操作する際に、センスの良い遊び心を加えることは可能です。次の例を考えてみましょう。マイクロインタラクションに有効な値を入力する際は注意してください。:invalid
値を指定すると、フォーム グループが揺れます。ただし、ユーザーがモーション設定を行っていない場合に限られます。
コンテンツ
この点については、コードサンプルで説明しました。では、ドキュメント フローで :has()
を使用するにはどうすればよいでしょうか。たとえば、メディアの周囲のタイポグラフィをどのようにスタイル設定するかについてアイデアが浮かびます。
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
が存在する場合、figcaption
は幅全体を占有し、余分な余白が追加されます。
状態への対応
マークアップの状態に応じてスタイルを変更する方法はいかがですか。たとえば、「従来型」のスライド式ナビゲーション バーの例を考えてみましょう。ナビゲーションの開閉を切り替えるボタンがある場合は、aria-expanded
属性が使用されることがあります。JavaScript を使用して、適切な属性を更新できます。aria-expanded
が true
の場合、:has()
を使用してこれを検出し、スライド ナビのスタイルを更新します。JavaScript がその役割を果たし、CSS はその情報を使って任意の処理を行うことができます。マークアップを並べ替えたり、追加のクラス名を追加したりする必要はありません(注: これは本番環境向けの例ではありません)。
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
:has はユーザーエラーを回避できますか?
これらの例に共通する点は何ですか?:has()
の使用方法を示している以外は、いずれもクラス名の変更は必要ありませんでした。それぞれ新しいコンテンツを挿入し、属性を更新しました。これは、ユーザーエラーを軽減できるという点で、:has()
の大きなメリットです。:has()
を使用すると、CSS が DOM の変更に適応する責任を負うことができます。JavaScript でクラス名を切り替える必要がないため、デベロッパーのエラーの可能性を低減できます。クラス名のスペルを間違えて、Object
ルックアップに保持しなければならないことは誰にでも起こり得ます。
興味深い考えですが、これにより、よりクリーンなマークアップとコードの削減につながるのでしょうか?JavaScript の調整が減るため、JavaScript の量が減ります。card card--has-media
などのクラスが不要になるため、HTML が削減されます。
既成概念にとらわれない考え方
前述のように、:has()
はメンタルモデルを破ることを推奨します。さまざまなことを試す機会です。限界を押し広げるための方法の一つとして、CSS のみでゲーム メカニクスを作成することもできます。たとえば、フォームと CSS を使用してステップベースのメカニズムを作成できます。
<div class="step">
<label for="step--1">1</label>
<input id="step--1" type="checkbox" />
</div>
<div class="step">
<label for="step--2">2</label>
<input id="step--2" type="checkbox" />
</div>
.step:has(:checked), .step:first-of-type:has(:checked) {
--hue: 10;
opacity: 0.2;
}
.step:has(:checked) + .step:not(.step:has(:checked)) {
--hue: 210;
opacity: 1;
}
これにより、興味深い可能性が開かれます。これを使用して、変換でフォームを走査できます。このデモは、別のブラウザタブで表示することをおすすめします。
楽しいゲームなら、定番の電線渡りゲームはいかがでしょうか?:has()
を使用すると、メカニクスを簡単に作成できます。ワイヤーにカーソルを合わせると、ゲームは終了します。はい。これらのゲーム メカニクスの一部は、兄弟コンビネーター(+
と ~
)などで作成できます。しかし、:has()
を使用すると、興味深いマークアップの「トリック」を使用せずに、同じ結果を得ることができます。このデモは、別のブラウザタブで表示することをおすすめします。
これらのプリミティブを本番環境にすぐに導入することはありませんが、プリミティブの使用方法を明確に示しています。:has()
を連結できるなどです。
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
パフォーマンスと制限事項
最後に、:has()
でできないことを確認しておきましょう。:has()
にはいくつかの制限があります。主な問題は、パフォーマンスの低下によって発生します。
:has()
を:has()
にすることはできません。ただし、:has()
は連結できます。css :has(.a:has(.b)) { … }
:has()
css :has(::after) { … } :has(::first-letter) { … }
内で疑似要素を使用しない- 複合セレクタのみを受け入れる疑似要素内の
:has()
の使用を制限。css ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- 疑似要素
css ::part(foo):has(:focus) { … }
の後の:has()
の使用を制限 :visited
を使用すると常に false になりますcss :has(:visited) { … }
:has()
に関連する実際のパフォーマンス指標については、こちらのグリッチをご覧ください。実装に関するこれらの分析情報と詳細を共有してくれた Byungwoo に感謝します。
これで完了です。
:has()
の準備をします。ぜひお友達に教えて、この投稿を共有してください。CSS の考え方を変える画期的な機能です。
すべてのデモは、こちらの CodePen コレクションで確認できます。