LAPLAB:Laugh And Peace Laboratory:あなたのWEB/IT担当にしてください。山形から全国までWEB/ITまるっとおまかせください。

お知らせ

CSSの:has()で「中身でスタイルを出し分ける」

2025.10.31お知らせ

CSSの:has()で「中身でスタイルを出し分ける」

リンクの中身に合わせて見た目を変えたい
たとえば「画像が入っているリンクは装飾ナシ、テキストだけのリンクには矢印を付けたい」。
そんな“中身で出し分け”を CSSだけで実現できるのが :has() です。この記事では、基礎から実例までを コピペOKのコードでサクッと解説します。
2025年の主要ブラウザで実用OK。


目次

  1. :has()ってなに?
  2. 最初の一歩:テキストだけのリンクにだけ矢印を付ける
  3. よくある実例(ナビ・画像リンク・フォーム・カード)
  4. とりあえず覚える注意点(アクセシビリティ&パフォーマンス)
  5. 古い環境のフォールバック
  6. コピペ用チートシート
  7. まとめ

1. :has()ってなに?

「子孫の状態を見て、親を選べる」 新しい擬似クラスです。
HTMLをいじらなくても、内側の有無や状態で外側の見た目を切り替えられます。

親セレクタ:has( 子孫セレクタ )
  • a:has(img)画像を含むリンク
  • a:not(:has(img))画像を含まないリンク
  • a:has(> img)直下の子img のリンク(> = 子限定)

ポイント:条件分岐がCSSだけで書ける。ちょっと感動します。


2. 最初の一歩:テキストだけのリンクにだけ矢印を付ける

「画像リンクはそのまま、テキストだけのリンクにだけ印を出す」— 最初のつまずきどころを一発解決。

/* 画像やSVGを含まない a にだけ ::before を表示 */
a:not(:has(img, svg))::before {
  content: "→";
  margin-right: .4em;
  color: currentColor;
}
  • <a>テキスト</a> → 「→ テキスト」
  • <a><img …></a> → 何も付かない

ひとこと:not(:has(...)) は「無い時」の条件。読みながら意味が追えるのが良いところ。


3. よくある実例(ナビ・画像リンク・フォーム・カード)

「実務でどこに効くの?」を短いコードで。

3-1. ナビ:子メニューを持つ項目だけ▼マーク

メニュー項目にサブメニューがある時だけ印を出す。HTMLはそのまま。

.nav > li:has(> ul)::after {
  content: "▾";
  margin-left: .4em;
  font-size: .9em;
}

3-2. 画像リンクだけ見た目を変える

画像を含むリンクだけ薄い枠線で区別したい時。

a:has(img) {
  outline: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  outline-offset: 2px;
}

3-3. フォーム:フォーカスが入ったフィールドを強調

中の入力にフォーカスがある時、ラッパーを光らせる。

.field:has(:focus-visible) {
  box-shadow: 0 0 0 3px color-mix(in srgb, #2563eb 30%, transparent);
}

3-4. ラベル:チェックされている時だけハイライト

<label> の中の input:checked に反応。ラジオ/チェックで有効。

label:has(input:checked) {
  background: color-mix(in srgb, #2563eb 12%, transparent);
  border-color: #2563eb;
}

3-5. カード:バッジを持つカードだけ余白追加

「バッジがあると窮屈」な時にサッと補正。

.card:has(.badge) { padding-top: 2rem; }

使い所イメージ:“特定の子を持つ時だけ装飾”。UIの微調整に最強。


4. とりあえず覚える注意点(アクセシビリティ&パフォーマンス)

4-1. ::before意味のあるテキストは入れない

装飾に使うのはOK。でも意味の本体はHTMLへ。読み上げで伝わらないことがあります。

4-2. セレクタの範囲は狭く

ページ全体に重い選択をかけるとコスト増。.article a:not(:has(img)) のようにスコープを絞ると安心。

4-3. 特異性が強くなりがち

:has() の中身の特異性を引き継ぎます。強すぎたら :where() で中身の特異性を0化。

.nav:has(:where(> ul)) { /* :where 内は特異性0 */ }

5. 古い環境のフォールバック

「全部のブラウザで同じに」を強く求めないなら、そのまま運用でOK。
どうしても必要ならこの2つ。

5-1. 構造で分ける(テキストを包む)

<a href="#"><span class="label">テキスト</span></a>
<a href="#"><img src="..." alt=""></a>
a > .label::before { content: "→"; margin-right: .4em; }

5-2. JSでクラス付け(数行)

document.querySelectorAll('a').forEach(a=>{
  if (a.querySelector('img,svg')) a.classList.add('is-img-link');
});
/* CSS */
a:not(.is-img-link)::before { content:"→"; margin-right:.4em; }

6. コピペ用チートシート

/* テキストだけのリンクに前置きアイコン */
a:not(:has(img, svg))::before { content:"→"; margin-right:.4em; }

/* 画像リンクだけ違う見た目に */
a:has(img) { outline: 2px solid color-mix(in srgb, currentColor 40%, transparent); }

/* 子メニューを持つ li だけ矢印 */
.nav > li:has(> ul)::after { content:"▾"; }

/* checked な input を含む label を強調 */
label:has(input:checked) { background: color-mix(in srgb, #2563eb 12%, transparent); }

/* 内側にフォーカスがあるフィールドを強調 */
.field:has(:focus-visible) { box-shadow: 0 0 0 3px color-mix(in srgb, #2563eb 30%, transparent); }

7. まとめ

  • :has() は「中身で親を選ぶ」。CSSだけで条件分岐できるのが最大の魅力。
  • a:has(img)画像ありa:not(:has(img))画像なし。ナビやフォームにも応用自在。
  • 実務では 装飾用途に限定、セレクタ範囲を絞って安全運用。
  • 困ったら フォールバック(構造 or JSクラス)でカバー。

「HTMLはそのまま、見た目だけサッと分けたい」— そんな時はまず :has() を思い出してみてください。コピペから始めて、少しずつレシピを増やしていきましょう。