:has() でできること|親を選ぶCSSで、画像の有無・チェック・数に合わせて見た目を変える、コピペ4種類と使いどころ

:has()でできること。画像があるカード・チェックした項目・数で並びが変わるタグの見本とコード

CSSで「チェックを入れたら、その項目のカードの色を変えたい」と思ったことはありませんか。これまでのCSSは、外側の箱から中の部品を選ぶことはできても、中の部品の状態を見て外側の箱を選ぶことはできませんでした。それを可能にしたのが :has() です。

使いどころは「中身や状態によって、見た目を変えたい外側の箱がある場面」です。画像の有無で並べ方を変える、選んだ項目を目立たせる、といった、これまでクラス名の付け分けや JavaScript が必要だった場面に使います。どのページでも同じ見た目でよい部品には使いません。

この記事の要点

  • :has() は「中に条件に合うものを持つ要素」を選ぶ書き方。画像の有無・チェックの状態・項目の数を見て、外側の箱の見た目を変えられる
  • 使いどころは、画像の有無で並べ方を変える一覧や、選んだ項目を目立たせるフォームなど、中身や状態で見た目を変えたい場面
  • 対応していないブラウザでは、:has() を含むルールがまるごと無視される。隠した入力欄など、見えないと困るものには @supports で代わりの書き方を用意する
トミー

連載「WEBの技術」の39回目です。
タブ切り替えや星の評価の回でも、:has() をこっそり使ってきました。今回は、その :has() そのものが主役です。

この記事の内容は、無料の道具でも試せます

擬似クラス チェッカー

:nth-child が「どこを選ぶか」が、図で分かる(登録なし)

道具で試してみる →

完成見本::has() でできること4種類

下の4つは、ページを開いた時点から自動で切りかわっています。1は画像を出したり消したり、2はチェック、3は選ぶ項目、4は項目の数を変えています。どれも、見た目の切りかえはCSSだけで行っています(見本を自動で動かすところだけ、短い JavaScript を使っています)。

※ 見本は読み上げソフトでは読まれないようにしています。

コピペ用のコード

HTMLは使いたい場所に、CSSはサイトのCSSファイルや、WordPressならテーマの「追加CSS」などに入れます。クラス名の頭の kd- は、サイトのほかのデザインと名前がぶつからないようにするための目印です。2と3の name や id は、同じページで2つ以上使うときは別の名前にしてください。

1. 画像があるときだけ、横に並べる

カードの中に画像があるときは「画像+文字」の横並び、画像がないときは文字だけの1列にする形です。.kd-card:has(> img) は「すぐ下に img を持っている .kd-card」という意味です。画像があるカードとないカードに、別々のクラス名を付けなくても、中身を見て並べ方が変わります。

HTML
<div class="kd-card">
  <img src="photo.jpg" alt="" width="1200" height="800">
  <div>
    <p class="kd-card-title">新しい施工事例を公開しました</p>
    <p class="kd-card-text">画像があるときだけ、横並びになります。</p>
  </div>
</div>
CSS
/* カードの土台(画像がないときは1列) */
.kd-card {
  display: grid;
  gap: 14px;
  padding: 16px;
  border: 1px solid #D8E6F0;
  border-radius: 12px;
  background: #fff;
}
/* 中に画像があるときだけ、画像と文字を横に並べる */
.kd-card:has(> img) {
  grid-template-columns: 96px minmax(0, 1fr);
  align-items: center;
}
.kd-card > img {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 8px;
}
.kd-card-title {
  margin: 0 0 4px;
  font-size: 16px;
  font-weight: 700;
  color: #10283A;
}
.kd-card-text {
  margin: 0;
  font-size: 14px;
  color: #5A6E7E;
}

2. チェックした項目の、枠の色を変える

チェックボックスを入れた項目の、カード全体の枠と背景の色を変える形です。これまでのCSSでは、チェックボックスより「あと」にある部品の色は変えられても、チェックボックスを包んでいる「外側の箱」の色は変えられませんでした。:has() を使うと、中の状態を見て、外側の箱を選べます。

HTML
<div class="kd-plans">
  <label class="kd-plan">
    <input type="checkbox" name="kd-opt" value="photo">
    <span>
      <span class="kd-plan-name">写真撮影</span>
      <span class="kd-plan-note">プロが撮影します</span>
    </span>
  </label>
  <label class="kd-plan">
    <input type="checkbox" name="kd-opt" value="text">
    <span>
      <span class="kd-plan-name">文章の作成</span>
      <span class="kd-plan-note">取材して原稿を書きます</span>
    </span>
  </label>
</div>
CSS
/* 選べる項目を、押しやすいカードにする */
.kd-plan {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 14px 16px;
  border: 2px solid #C9D3DB;
  border-radius: 12px;
  background: #fff;
  cursor: pointer;
  transition: border-color .2s, background-color .2s;
}
.kd-plan + .kd-plan {
  margin-top: 8px;
}
.kd-plan input {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 2px 0 0;
  accent-color: #0369A1;
}
/* 中のチェックボックスにチェックが入ったら、カードの枠と背景を変える */
.kd-plan:has(input:checked) {
  border-color: #0284C7;
  background: #F0F9FF;
}
/* キーボードで選んでいるときは、カードのまわりに枠を出す */
.kd-plan:has(input:focus-visible) {
  outline: 3px solid #0284C7;
  outline-offset: 2px;
}
.kd-plan-name {
  display: block;
  font-weight: 700;
  color: #10283A;
}
.kd-plan-note {
  display: block;
  font-size: 14px;
  color: #5A6E7E;
}
@media (prefers-reduced-motion: reduce) {
  .kd-plan {
    transition: none;
  }
}
/* 色を強制する設定でも、選んだカードの枠を区別する */
@media (forced-colors: active) {
  .kd-plan:has(input:checked) {
    border-color: Highlight;
  }
}

3. 「その他」を選ぶと、入力欄が出る

ラジオボタン(いくつかの中から1つを選ぶ部品)で「その他」を選んだときだけ、くわしく書く入力欄を出す形です。これまでは JavaScript で書くことが多かった動きですが、:has() で「その他が選ばれている質問のまとまり」を選び、その中の入力欄を表示しています。

HTML
<fieldset class="kd-q">
  <legend>このサイトを知ったきっかけ</legend>
  <label><input type="radio" name="kd-from" value="search"> 検索</label>
  <label><input type="radio" name="kd-from" value="sns"> SNS</label>
  <label><input type="radio" name="kd-from" value="other"> その他</label>
  <div class="kd-other">
    <label for="kd-from-text">くわしく教えてください</label>
    <input type="text" id="kd-from-text" name="kd-from-text">
  </div>
</fieldset>
CSS
.kd-q {
  margin: 0;
  padding: 0;
  border: 0;
}
.kd-q legend {
  margin-bottom: 8px;
  padding: 0;
  font-weight: 700;
}
.kd-q > label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 16px;
}
.kd-q input[type="radio"] {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: #0369A1;
}
/* 入力欄は、ふだんは隠しておく */
.kd-other {
  display: none;
  margin-top: 12px;
}
/* 「その他」が選ばれたときだけ、入力欄を出す */
.kd-q:has(input[value="other"]:checked) .kd-other {
  display: block;
}
.kd-other label {
  display: block;
  margin-bottom: 4px;
  font-size: 14px;
}
.kd-other input {
  box-sizing: border-box;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #7B8A97;
  border-radius: 8px;
  font-size: 16px;
}
/* :has() に対応していないブラウザでは、最初から出しておく */
@supports not selector(:has(*)) {
  .kd-other {
    display: block;
  }
}

4. 数が多いときは、並べ方を変える

項目が4個までは横に流して並べ、5個以上になったら小さくして2列にそろえる形です。li:nth-child(5)(5番目の li)を持っているかどうかで、「5個以上あるか」を調べています。項目の数が記事ごとに変わる場所でも、数に合わせて並べ方が切りかわります。

HTML
<ul class="kd-tags">
  <li>WordPress</li>
  <li>デザイン</li>
  <li>CSS</li>
  <li>SNS</li>
  <li>AI</li>
  <li>写真</li>
</ul>
CSS
/* タグの土台(少ないときは、文字の長さのまま横に並べる) */
.kd-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.kd-tags li {
  padding: 8px 14px;
  border-radius: 999px;
  background: #E0F2FE;
  color: #0C4A6E;
  font-size: 15px;
  font-weight: 700;
}
/* 5個目があるとき(=5個以上)は、小さくして2列にそろえる */
.kd-tags:has(> li:nth-child(5)) {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
}
.kd-tags:has(> li:nth-child(5)) li {
  padding: 6px 10px;
  font-size: 13px;
  text-align: center;
}

仕組み:「〇〇を持っている箱」を選ぶ

:has() のかっこの中は「条件」

A:has(B) は、「B を中に持っている A」を選びます。選ばれるのは、かっこの外の A のほうです。たとえば .kd-plan:has(input:checked) なら、「チェックが入った input を中に持っている .kd-plan」、つまりチェックされた項目のカードが選ばれます。

かっこの中は、ふつうのCSSと同じ書き方で条件を書けます。:checked(チェックが入っている)、[value="other"](value が other)、:nth-child(5)(5番目)のような書き方を組み合わせられます。

「すぐ下だけ」「すぐ次だけ」と範囲をしぼれる

かっこの中の頭に > を付けると「すぐ下(子)だけ」、+ を付けると「すぐ次の兄弟だけ」を調べます。1の .kd-card:has(> img) は、カードのすぐ下にある画像だけを見ています。文字の中に小さなアイコン画像が入っていても、そちらには反応しません。

書き方意味使う例
A:has(B)A の中のどこかに B があるフォームの中に、まだ入力されていない項目がある
A:has(> B)A のすぐ下に B があるカードのすぐ下に画像がある(1)
A:has(+ B)A のすぐ次に B がある見出しのすぐ次に画像が来るときだけ、見出しの下の余白を変える
A:not(:has(B))A の中に B がない画像がないカードだけに、色の帯を付ける

数を調べるときは「〇番目があるか」

4の .kd-tags:has(> li:nth-child(5)) は、「5番目の li があるか」を調べています。5番目があれば、項目は5個以上です。ここで気をつけたいのは、display: none で隠した li も数に入ることです。:nth-child() は、見えているかどうかではなく、HTMLに何番目に書かれているかで数えます。

対応していないブラウザでは、ルールごと無視される

MDNによると、:has() は Chrome 105・Safari 15.4・Firefox 121 から使え、2023年12月から主要なブラウザすべてで使える状態(Baseline)になっています。ただ、対応していないブラウザでは、:has() を含むルールがまるごと無視されます。

1・2・4は、無視されても「ふつうの見た目のまま」になるだけです。困るのは3で、入力欄が一度も出てこなくなります。そこで @supports not selector(:has(*))(:has() に対応していないときだけ当てる書き方)の中で、入力欄を最初から表示しています。

数値を変えるとどうなるか

:has() をどこに付けるか

同じ「写真撮影にチェック」の状態でも、:has() を付ける場所で、色が変わる範囲が変わります。カードに付けると選んだ1枚だけ(左)、項目をまとめた箱に付けると「どれか1つでも選ばれていたら」まとまり全体(中央)、カードの中の名前に付けると文字の色だけ(右)が変わります。どれを選んだかをはっきり見せたいなら、カードに付けるのが分かりやすい形です。

いくつから切りかえるか

4のCSSは、5番目があるかどうかで切りかえています。4個まではそのまま横に流れ、5個になったところで2列に変わります。:nth-child(5) の数字を変えれば、切りかえる数を変えられます。タグが3〜4個で終わることが多いサイトなら5、もっと多いなら8のように、ふだん並ぶ数に合わせます。

変えるところ指定今回の値目安
画像の幅(1)grid-template-columns の1つ目96pxスマホでも文字が1行10字ほど入る幅
選んだ枠の色(2)border-color#0284C7白い背景と3:1以上
切りかえる数(4):nth-child()5ふだん並ぶ数より少し多め
色が変わる速さ(2)transition0.2秒0.15〜0.3秒

スマホと、見る人への配慮

色だけで「選んだ」を伝えない

2はカードの色が変わりますが、色の差だけでは、色が見分けにくい人には伝わりません。WCAG(ウェブのアクセシビリティの国際的な基準)2.2の達成基準1.4.1では、色だけで情報を伝えないこととされています。見本では、本物のチェックボックスを見えるまま残し、チェックの印でも選んだことが分かるようにしています。選んだ枠の色 #0284C7 は、白い背景と4.1:1の差があります。

キーボードで操作している場所を見せる

2では、.kd-plan:has(input:focus-visible) で、キーボードで選んでいる項目のカード全体に青い枠を出しています。チェックボックスは小さいので、カードの外側に枠を出したほうが、どこにいるかが見つけやすくなります。これも「中の状態を見て、外側の箱を変える」使い方です。

隠した入力欄は、読み上げにも出ない

3の入力欄は display: none で隠しています。この指定で隠したものは、画面に出ないだけでなく、Tabキーでも移動できず、読み上げソフトにも伝わりません。「その他」を選ぶ前に、使わない入力欄へ移動してしまうことがないので、この場面には合っています。

色を強制する設定では、枠の色をOSの強調色に

Windowsのハイコントラストなど、OSが色を決める設定(強制カラーモード)では、背景色が消え、枠の色もOSの色にそろえられます。2では、選んだカードの枠だけ、OSの強調色 Highlight にしています(Chromeの再現機能で確認しました)。

:has() を広い範囲に付けすぎない

MDNには、:has() の使い方によっては、ページの表示の速さに影響することがあると書かれています。body:has(…) のように中身の多い要素に付けるのは避け、> や + で調べる範囲をしぼる書き方が勧められています。今回の見本も、カードや質問のまとまりなど、小さな箱に付けています。

トミー

:has() は便利なので、つい何でも :has() で書きたくなります。
でも、クラス名を1つ付ければすむところは、クラス名のほうが読みやすいこともあります。「中身が変わるから :has() で書く」と、理由を言えるときに使うのがいいと思います。

AIに頼むなら

同じような部品は、Claudeなどの生成AIに頼んでもすぐ作れます。そのときは、何を見て、どの箱を変えたいのかまで伝えると、余計な JavaScript が入りにくくなります。

申し込みフォームのオプションを、チェックボックス付きのカードにして。
チェックしたカードの枠と背景を、:has() を使ってCSSだけで変えたい。
チェックボックスは見えるまま残し、キーボードで選んでいるカードにも枠を出して。

AIが出したコードは、そのまま使う前に、次の3つを確かめます。

  • :has() に対応していないときに、困ることがないか。隠した入力欄やボタンが、一度も出てこなくならないかを見ます。必要なら @supports で代わりの書き方を足します
  • body:has(…) や :root:has(…) のように、広い範囲に付けていないか。ページ全体を見張る形になり、表示の速さに影響することがあります
  • 本物のチェックボックスやラジオボタンを、消していないか。見た目を整えるために display: none で消すと、キーボードで選べなくなります

KAKERUならこう使う/使わない

最後に、作り手としての判断です。

使う場面

  • 画像がある記事とない記事が混ざる、お知らせやブログの一覧。WordPressで更新する人が、画像を入れるかどうかを気にしなくても、一覧の並びが崩れないようにできます
  • 申し込みや問い合わせフォームの、選ぶ項目。選んだものがひと目で分かるようにして、送る前の見直しを楽にします
  • 「その他」など、選んだときだけ必要な入力欄。使わない人には見せないので、フォームが短く見えます

使わない場面

  • いつも同じ見た目でよい部品。中身で変わらないなら、ふつうのクラス名で書くほうが、あとから読む人にも分かりやすいです
  • 送信ボタンを押せなくするなど、動きそのものを止めたいとき。CSSで薄く見せても、ボタンは押せてしまいます。押せないようにするのは HTML や JavaScript の役目です
  • ページ全体の見た目を、どこかの小さな部品の状態で切りかえるとき。body に :has() を付ける形になりやすく、どこで見た目が変わっているのか、あとから追いにくくなります
トミー

KAKERUなら、WordPressで更新する人が「何もしなくても、きれいに並ぶ」ところに :has() を使います。
画像を入れ忘れても、タグが増えても、一覧が崩れない。更新する人の手間を減らすための :has() です。

まとめ

  • :has() は「中に条件に合うものを持つ要素」を選ぶ書き方。中身や状態を見て、外側の箱の見た目を変えられる
  • 画像の有無(:has(> img))、チェックの状態(:has(input:checked))、項目の数(:has(> li:nth-child(5)))を条件にできる
  • 対応していないブラウザではルールごと無視される。隠したものが出てこなくなる場合は、@supports で代わりの書き方を用意する
  • 色だけで状態を伝えず、本物のチェックボックスを残す。キーボードで選んでいる場所も見せる
  • body などの広い範囲には付けず、> や + で調べる範囲をしぼる

まずは1の「画像があるときだけ、横に並べる」をコピーして、画像を消したり戻したりしてみてください。

参考にした資料

いずれも2026年11月2日に確認しました。ブラウザの対応状況は変わることがあります。最新の情報は各ページでご確認ください。

ふだんの制作の話や、作ったものの裏側は Instagram と X でも出しています。

関連記事

押すと切り替わるタブをCSSだけで コピペで使える5種類と、使う・使わないの判断
星の評価をCSSで作る。小数まで塗った星・件数つきの星・押して選ぶ星の見本とコード
チェックボックスの見た目をCSSで変える。見本とコード
入力欄を選んだら色が変わる。見本とコード
:has()でできること。画像があるカード・チェックした項目・数で並びが変わるタグの見本とコード

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

CAPTCHA


ABOUTこの記事をかいた人

25歳からWEBの世界に携わり、WEBデザインから始めて、今はWEBシステムまで一貫して制作しています。問題点を洗い出し、改善を提案する中で、WEB・グラフィック・動画・システムを縦断して提案するのが得意です。