国王ですら
顔面蒼白
CSS史上初めて。
下から上を従わせる
下克上な疑似クラス:has()

make_king_face_turn_pale  key visual by Yohiichiroh Kohtani | digivalley
Toplabo
Make King Face Turn Pale

この記事は『レスポンシブは線から面へ、そして空間へ』の続きになります。先にそちらをご覧ください。

理論上はすべての要素を、任意の子要素が制御できる:has()疑似クラス

 | 2026/06/06

概要

:has()疑似クラスはCSSのIF文

  • 子孫の状態を監視し、ルールに従って自分も含めて制御する

    よく言われるのは、:has()疑似クラスはCSSのIF文。まさにその通りで、条件を設定しその条件を満たせば子孫の要素に対してスタイルを制御することが可能になる。条件には、特定の子要素やクラス、IDなどを含むか含まないかなどは当然だけど、CSS の状態も含まれ、:hover,:activeなどに加え :focus, :checked, などのフォーム系、:target,:empty,やdetails系の:openなど多くあり、簡単なIF文なら:has()疑似クラスがあればJS使う必要はなくなった。特にフォーム系は昔はフォームパーツにフォーカスしたり、チェックしたりしても親に伝えるにはJSが必要だったけど、今は:has()疑似クラスを使えばCSSだけで判定できるようになった。
    さらに厳密にいうと、子が親を制御できるようになったというより、子が先祖だけでなく、枝分かれの兄弟やいとこまで制御できるようになったという方が正確といえる。bodyを:has()疑似クラスの親にすれば理論上はすべての要素が、任意の子要素の制御対象になるからね。

:has()疑似クラスの簡単な紹介

  • 簡単な紹介

    まだ:has()疑似クラスをよく知らないという方のために、MDNで紹介されている内容を引用して紹介してみよう。
    たとえば、下記の様なHTMLがある。

    <header>
    	<h1>とある王国の物語</h1>
    </header>
    <section>
    	<article>領民のとるべき道は</article>
    </section>
  • 通常、こういう場合のCSSはh1とarticleの間隔はheaderとsectionで空けるのでh1にはマージンは入れない。

    h1{
      margin-bottom: 0;
    }
  • しかし、状況によってh1の直後にp要素のリードが付いたり付かなかったりする場合があるとする。
    この場合、つまりh1の直後にp要素が来たらh1のマージンがゼロなのでp要素とくっついてしまう。そのためh1自身にマージンを追加して、直後のp要素との間隔を空ける必要がある。

    <header>
    	<h1>とある王国の物語</h1>
    	<p>今、ハズクラス疑似王国は物価高が極まり<br>領民は日々の生活にあえいでいた。</p>
    </header>
    <section>
    	<article>領民のとるべき道は</article>
    </section>
  • そこで:has()疑似クラスの登場、下記のように記述することで、h1直下にp要素がある場合にマージンを調整することができるようになる。

    h1:has(+ p) {
      margin-bottom: 20px;
    }
  • CSSのif文

    :has()疑似クラスを使えば、『もしもh1の直下にp要素が来たらマージンを追加する。』というまるでIF文のような設定を入れられるんだ。
    これが『:has()疑似クラスはCSSのIF文』と呼ばれるゆえんだね。
    他にもいくつかサンプルがあるのでMDNを見ておくと良いと思う。
    MDNの:has()疑似クラス解説ページ:https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Selectors/:has

:has()疑似クラスができないこと

  • 不可能はないような:has()疑似クラス、だけどできないこともそりゃああります。

    無敵の:has()疑似クラス、だけどできないことがあるんだよ。
    たとえば:has()疑似クラスは、マウスオーバーには対応していても、クリックのようなイベントには対応してない。そもそもCSSには『クリックされた状態を保持する』仕組みがほぼないからね。
    近いものには:focusや:checkedなどがあるけど、クリックと違い、:focusはマウスが外れると元に戻ってしまう。:activeもそうだね。
    他にアンカー要素だとハッシュとtarget疑似クラスを使えば、なんとかなりそうだけど、スクロールの問題があり微妙(『ついにハッシュはGPSへ進化』で紹介したね)。
    また他の要素button、details要素などを使う方法もあるけど、アンカー要素じゃなくて専用要素が必要などがあり、かなり微妙。
    :checkedもinput要素が必要だからね。普通の文章に使うのはいろいろ問題がありそうだ。
    早い話CSSには:hoverや:checkedはあるが:clickedという疑似クラスはないんだよねえ。そのうちできないかなあ。

    で、現在の結論、クリックについてはやはりJSを併用するほうがスマート。何もJSはライバルってわけじゃないんだからどんどんお互いにサポートしあってスマートでエレガントなコードを書いていこう
    そもそもCSSだけでやるんだ!ってそんな縛りはないので。
    まあ、どうしてもCSSだけでクリック&:has()疑似クラスをやりたいって場合はdetailsか:checkedを使って何とかするしかないかもね。
    で、次にその:checkedを使ったデモを紹介。

国王も
顔面蒼白
デモ

さっそく、国王を顔面蒼白にさせるデモンストレーション

:checked疑似クラスを監視し、その状態によって親のスタイルを制御する

  • これまでJSを使わないとできなかったことが可能に

    これまでは、JSを使ってイベントを取得し、イベントの内容を分析してクラスなどで制御していたことが、たった1行のCSSコードで可能になったよ。革命といえるよね。しかも最初の章でも触れたけど、親に限らずほぼすべての要素を制御可能だからね。上のデモでも、少々修正すれば3つの内のどれかをクリックすると、国王の顔色に限らず、どの要素でもスタイルを変更することができるようになる。

顔面蒼白
デモの
解説

顔面蒼白デモのコード解説

  • みやすいようにコードを少々変更

    以下、使用コード。実装版はネスト化してコーディングしてあるので、見やすい様に元に戻して掲載した。

  • HTMLの解説

    王に対する領民の行動をチェックボックスで選べるようにinput要素を3種類用意。
    input要素にそれぞれallegiance、escape、revolutionのID名を付けてクリックできるように設置。

    <!-- HTML -->
    
    	<div id="king" class="box">
    		<header class="castle">
    			<div class="title">国王</div>
    			<div id="king_avator" class="contents"></div>
    		</header>
    		<div class="kingdom">
    			<div class="title" id="knight">領民のとるべき道は</div>
    			<ul id="decision">
    				<li>
    					<input id="allegiance" type="radio" name="action">
    					<label for="allegiance">何が起きようと王に忠誠を誓おう:</label>
    				</li>
    
         <!-- 以下同様に残り2つのラジオボタンにもチェックボタン設置 -->	
        </ul>
    		<div id="character"><img src="../images/hammerLady.webp" alt=""></div>
    	</div>
    </div>
  • :has()疑似クラスの設定

    ポイントはここinput要素がchecked状態になったら王様の顔色を変更するように:has()疑似クラスで設定。
    checkボタンはどれか一つだけを選択できるようにしたいのでラジオボタンを使用。
    ボタンは、『忠誠、逃亡、革命』の3個設置。下記のように記述することでどのボタンがチェックされたか分かるようにした。

    #king:has(#allegiance:checked){ /*王の顔色を肌色へ変更*/}
    #king:has(#escape:checked){/* 王の顔色を赤色へ変更 */}
    #king:has(#revolution:checked){/* 王の顔色を青へ変更 */}
    
    /* 実際は下記のように他のスタイルも記載 */
    #king:has(#allegiance:checked){ 
    	#king_avator{
    		background: #c9f0af;
    		svg #darma_face{
    			fill: #fde8eb;
    		}
    	}
    }
  • 中世ファンタジー風にhas疑似クラスを解説

    さて、ここでもう一度重要な点をおさらい。分かりやすく中世ファンタジー風に解説するよ。
    ポイントはこのコード#king:has(#revolution:checked){/* 王の顔色を変更 */}
    これは:has()が設定されているのが#kingだから、領民が革命を選択すると王様が顔面蒼白になるんだ。
    .kingdomだとこうはいかない。王様迄届かないんだよ、領民が何をやっても、王様の顔色は変わらない。#kingだからできるんだよ。
    つまりこれは、領民が革命を行うことを王様自身が許容しているんだよね。
    これってすごくない?
    だって、普通の国王は自分が法律だよね。この国だってこれまでの国王は自分が法律だったはずだよ。だけど、疑似クラスHAS王は違う、賢王なんだよ。国を栄えさせるためにはどうすればいいか。
    初めてすべての者が法のもとでは平等であることをhasクラスで宣言し、王でさえ、法の前では領民と同じく従うことを誓ったんだよ。
    まさに賢王だね。
    ・・・と、中世ファンタジー風に:has()疑似クラスを紹介しました。

影響
一覧

甚大な被害を受けたCSS達の一覧

  • :has()疑似クラスの登場で、存在が危ぶまれている他のCSS達の一覧

    has疑似クラスはやぱり、革命的で優秀なので影響を受けたCSSがいっぱいあるんだよね。早い話『必要なくねえ?』ってやつだね。
    次にその一覧を表示にしたよ。


    CSS本来の役割hasがあると


    :focus-within 子孫にfocusがあるか検知 :has(:focus)で代用可能


    :target-within 子孫にtargetがあるか検知 :has(:target)で代用可能


    :checked連携 チェックボックスハック :has(input:checked)の方が自然


    :valid連携 フォーム状態反映 form:has(:valid)で親まで伝播


    :invalid連携 エラー表示 form:has(:invalid)で十分


    :placeholder-shown連携 入力済み判定 :has(input:not(:placeholder-shown))


    :empty 子要素有無判定 条件によってはhasで代替


    details[open] 開閉状態判定 details:has(summary:focus)など柔軟化


    :nth-child()の一部 子要素数で分岐 hasの組み合わせで代替可能


    隣接セレクタ + 前後関係判定 checkboxなどの見た目をカスタマイズするハックは不要


    一般兄弟 ~ 兄弟関係判定 hasの方が読みやすい場合あり

  • 補足

    たとえば上の隣接セレクタの『+』の説明。これは下記の様なハックが昔はよく使われてた。
    「input の後ろに label を置いて input:checked + label::before でスタイルを当てる」という考え。
    inputの直後にlabel要素を置くのが必須。

    <!-- HTML -->
    <label class="custom-checkbox">
      <input type="checkbox" />
      <span>チェックする</span>
    </label>
    
    
    <!-- CSS -->
     <style>
    /* チェックボックス自体を非表示にし、隣接ラベルでスタイルを定義するCSS */
    
    /* 以下二つのスコープがこのハックの要 */
    
    .hidden-checkbox:checked + .checkbox-label::before { 
    	background: #007bff; 
    }
    
    .hidden-checkbox:checked + .checkbox-label::after { /* チェックマーク */
      content: ""; 
    	position: absolute; 
    	left: 7px; 
    	top: 3px;
    	display: block;
      width: 6px; 
    	height: 11px; 
    	border: solid white;
      border-width: 0 3px 3px 0; 
    	transform: rotate(45deg);
    }
    
    /* 以下はinput要素を隠したり、チェックアイコンを作成するCSS今回のテーマとは関係ない */
    
    .hidden-checkbox { /* 画面外へ隠す */
      position: absolute; 
    	width: 1px; 
    	height: 1px; 
      padding: 0; 
    	margin: -1px; 
    	overflow: hidden; 
    	clip: rect(0, 0, 0, 0); 
    }
    .checkbox-label { 
    	position: relative; 
    	padding-left: 28px; 
    	cursor: pointer; 
    }
    .checkbox-label::before { /* ボックス枠 */
      content: ""; 
    	position: absolute; 
    	left: 0; 
    	top: 0;
    	display: block;
      width: 24px;
      height: 24px;
      border: 2px solid #ccc;
      border-radius: 6px;
      transition: all 0.2s ease;
    }
    
    .hidden-checkbox:checked + .checkbox-label::before { 
    	background: #007bff; 
    }
    
    .hidden-checkbox:checked + .checkbox-label::after { /* チェックマーク */
      content: ""; 
    	position: absolute; 
    	left: 7px; 
    	top: 3px;
    	display: block;
      width: 6px; 
    	height: 11px; 
    	border: solid white;
      border-width: 0 3px 3px 0; 
    	transform: rotate(45deg);
    }
    </style>
    
  • :has()疑似クラスで書き換えると

    これが:has()疑似クラスの登場で、下記のように直感的に自由に記述することが可能になった。
    コードの量も多少は減った。

    <!-- HTML -->
    <label class="custom-card">
      <input type="checkbox">
      <span>チェックする</span>
    </label>
    
    <!-- CSS -->
    <style>
    /* 疑似クラス:has() を使った状態変化 */
    /* チェックされたinputを持つ親(.todo-item)の中の、カスタムボックスを変化させる */
    
    .todo-item:has(.real-checkbox:checked) .custom-box {
      background-color: #007bff;
      border-color: #007bff;
      transform: scale(1.05); 
    }
    
    /* チェックマークを描画 */
    .todo-item:has(.real-checkbox:checked) .custom-box::after {
      transform: rotate(45deg) scale(1);
    }
    
    
    /* 以下はinput要素を隠したり、チェックアイコンを作成するCSS、今回のテーマ『:has()疑似クラス』とは関係ない */
    
    
    /* 全体のレイアウト */
    .todo-item {
      display: flex;
      align-items: center;
      gap: 12px;
      cursor: pointer;
      padding: 10px;
    }
    
    /* 本物のチェックボックスは非表示に */
    .real-checkbox {
      position: absolute;
      opacity: 0;
      width: 0;
      height: 0;
    }
    
    /* 1. カスタムチェックボックスの土台 */
    .custom-box {
      width: 24px;
      height: 24px;
      border: 2px solid #ccc;
      border-radius: 6px;
      position: relative;
      transition: all 0.2s ease;
    }
    
    /* 2. チェックマークの線(擬似要素で作成) */
    .custom-box::after {
      content: "";
      position: absolute;
      left: 7px;
      top: 3px;
      width: 6px;
      height: 11px;
      border: solid white;
      border-width: 0 3px 3px 0;
      transform: rotate(45deg) scale(0); /* 初期状態は非表示 */
      transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    }
    </style>
    
  • 便利なポイント

    :has()疑似クラス登場前のハックでは、 input が必ず先頭にないとCSSで制御できなかったが、:has() 登場後は input がテキストの後ろにあろうが、深い階層(子孫要素)にあろうが関係なくchecke状態を検知できるようになった。
    label で input を囲むシンプルな構造にするだけで動作するため、管理が非常に楽。

  • このテーマは今回はここまで

    このテーマ、『今までの面倒な記述を:has()疑似クラスを使えばこんなに楽になるよ、や、できるようになったよ』は、ほんとにたっくさんあるので、また折を見て紹介しようと思う。

『国王ですら顔面蒼白』関連のお薦め

このサイトで紹介しているコード、プログラムなどは個人の学習目的で作成されたものであり、いかなる保証も行いません。
利用はすべて自己責任でお願いします。
ただし、このページで紹介しているプログラムやビジュアルなどはご依頼いただければ実装を賜ります。
お問い合わせはこちら