CSSの:has()を使うと、「画像を含むカード」「入力エラーがあるフォーム」「直後に段落がある見出し」のように、内側や後続の要素を条件として対象を選べます。以前はJavaScriptや追加クラスが必要だった表現も、HTMLの状態に応じてCSSだけで切り替えられます。本記事では基本構文、実用例、対応状況、詳細度、使う際の注意点をまとめます。
CSSの:has()とは
:has()は、括弧内へ渡した相対セレクタが1つ以上一致するとき、:has()を付けた要素を選択する関係擬似クラスです。親要素を選べる機能として紹介されることが多いですが、子孫だけでなく、直後や後続の兄弟要素も条件にできます。
対象にしたい要素:has(条件) {
/* 条件に一致した対象へ適用するスタイル */
}
たとえば、画像を含むarticleだけを選択するCSSは次のとおりです。
article:has(img) {
border-color: #087e8b;
}
通常のarticle imgが選ぶのは内側の画像です。一方、article:has(img)が選ぶのは条件を満たしたarticle自身です。
子孫と直接の子を使い分ける
/* どの階層でもimgを含めば一致 */
.card:has(img) { }
/* 直下にimgがある場合だけ一致 */
.card:has(> img) { }
>を付けると直接の子だけを条件にできます。意図しない深い階層の画像まで拾いたくない場合に便利です。
画像の有無でカードのデザインを変える
CMSでは、カードにアイキャッチ画像がある場合とない場合が混在します。これまではcard--has-imageのような追加クラスを出力することがありましたが、:has()ならHTMLの内容から判定できます。
HTML
<article class="card">
<img src="sample.jpg" alt="記事のアイキャッチ">
<div class="card-body">
<h2>記事タイトル</h2>
<p>記事の説明文です。</p>
</div>
</article>
<article class="card">
<div class="card-body">
<h2>画像がない記事</h2>
<p>こちらには画像がありません。</p>
</div>
</article>
CSS
.card {
padding: 24px;
border: 1px solid #dce7ea;
border-radius: 8px;
}
.card:has(> img) {
display: grid;
grid-template-columns: 160px 1fr;
gap: 20px;
border-color: #087e8b;
}
.card:has(> img) > img {
width: 100%;
height: 100%;
object-fit: cover;
}
画像があるカードだけ2列レイアウトになり、画像がないカードは通常の1列表示を保ちます。コンテンツ側で修飾クラスを管理する必要がないため、画像を追加・削除したときも表示が自動で変わります。
フォームや見出しで使える実用例
フォーカス中の入力欄を含む行を強調する
<div class="form-row">
<label for="email">メールアドレス</label>
<input id="email" type="email" required>
</div>
.form-row:has(input:focus-visible) {
background: #eef8f9;
border-color: #087e8b;
}
入力欄にキーボードフォーカスが表示されているとき、親の.form-row全体を強調できます。フォーカスリング自体は消さず、入力中の場所を補助的に示すデザインとして使います。
入力エラーがある行を選ぶ
.form-row:has(input:user-invalid) {
border-color: #c43d3d;
background: #fff2f2;
}
:user-invalidは、利用者の操作後に値が検証条件を満たさない入力欄へ一致します。対応環境を考慮して:invalidを使う場合は、ページを開いた直後から必須項目がエラー表示にならないか確認してください。
直後に段落がある見出しの余白を変える
h2:has(+ p) {
margin-bottom: 8px;
}
+は直後の兄弟要素を表します。この指定は、すぐ後ろにpがあるh2を選択します。通常のh2 + pが段落を選ぶのに対し、h2:has(+ p)は見出しへ戻ってスタイルを適用できる点が違います。
チェック状態を親コンテナへ反映する
.option:has(input:checked) {
border-color: #087e8b;
background: #eef8f9;
}
ラジオボタンやチェックボックスを含む選択カードで、選ばれた項目全体を強調できます。ただし、見た目だけで状態を伝えず、入力要素、ラベル、フォーカス表示を適切に残してください。
対応ブラウザとフォールバック
MDNでは:has()を「Baseline Widely available」とし、主要ブラウザをまたいで2023年12月から利用可能になった機能として案内しています。現在の一般的なブラウザでは使いやすくなりましたが、古いブラウザや組み込みブラウザを対象にする場合は確認が必要です。
:has()へ未対応のブラウザでは、そのセレクタを含むルール全体が適用されません。そのため、基本表示を先に書き、:has()は追加の見た目として使う方法が安全です。
/* 基本表示:未対応環境でも成立させる */
.card {
display: block;
}
/* 対応環境だけレイアウトを拡張する */
@supports selector(.card:has(> img)) {
.card:has(> img) {
display: grid;
grid-template-columns: 160px 1fr;
}
}
対応状況は対象ユーザーのブラウザ構成によって判断します。重要な情報の表示やフォーム送信など、機能そのものを:has()だけに依存させず、未対応でも内容を読んで操作できる状態を保ちましょう。
詳細度と書き方の注意点
:has()の詳細度は引数の影響を受ける
:has()自体が擬似クラス分の詳細度を加えるのではなく、括弧内で最も詳細度が高いセレクタの値を引き継ぎます。
/* .card と #featured の影響を受け、強い指定になる */
.card:has(#featured) {
border-color: red;
}
/* クラスだけを条件にすると管理しやすい */
.card:has(.featured) {
border-color: teal;
}
引数にIDセレクタを入れると意図せず上書きしにくくなることがあります。再利用する部品では、必要以上に強いセレクタを避けます。
:has()の入れ子はできない
/* 無効::has()の中へ:has()は書けない */
.card:has(.content:has(img)) { }
/* 複数条件は連結できる */
.card:has(.content):has(img) { }
また、::beforeや::afterなどの擬似要素は、原則として:has()内の条件にできません。
範囲を絞ったセレクタを書く
body:has(...)や:root:has(...)はページ全体の状態を扱えますが、必要のない広い探索はコードの意図を分かりにくくします。カードなら.card:has(> img)、フォーム行なら.form-row:has(input:user-invalid)のように、対象と条件を具体的に書くと保守しやすくなります。
まとめ
:has()は、内側や後続の要素を条件にして、親要素や前の兄弟要素を選択できるCSSの関係擬似クラスです。画像付きカード、入力状態、チェック状態、見出しの余白などを追加クラスなしで表現できます。基本表示を先に用意し、対応状況、詳細度、アクセシビリティを確認しながら段階的に取り入れましょう。