:has()でフォームはどこまで書ける?JSを減らす状態連動スタイリング

はじめに

フォームUIの状態表現は、気づくと JavaScript が増えがちです。フォーカス、エラー、入力済みなどの軽い条件までJSで管理すると、保守時に「見た目だけ直したいのにロジックまで触る」状態になりやすくなります。

:has() を使うと、子要素の状態を親側で判定できるため、UIの装飾ロジックをCSSへ戻せます。この記事では、最小例から実務向けのバリデーション表示まで段階的に組み立てます。

使い方

:has() は関係セレクタで、特定条件を満たす子要素を持つ親要素にスタイルを当てられます。フォームでは「入力中」「妥当性エラー」「チェック済み」などを親ラッパーに反映する用途が定番です。

メリットは、状態判定の分岐をHTML/CSSの近くに置けることです。デザイン変更時にJSファイルへ飛ばずに調整しやすくなります。

まずは入力フォーカス時にラベル全体を強調する最小例です。input:focus を持つ .field を直接装飾します。

See the Pen css :has() by watanabe (@web-sourcecode) on CodePen.

HTML

HTML
<label class="field">
  <span class="field__label">メールアドレス</span>
  <input class="field__control" type="email" required>
</label>

CSS

CSS
.field {
  display: grid;
  gap: 6px;
  padding: 12px;
  border: 1px solid #cbd5e1;
  border-radius: 10px;
  transition: border-color 180ms ease, box-shadow 180ms ease;
}

.field:has(.field__control:focus) {
  border-color: #0ea5e9;
  box-shadow: 0 0 0 3px rgb(14 165 233 / 15%);
}

:has() はフォームに限らず、チェック済みカード、選択中メニュー、入力補助UIなどで役立ちます。特に「親の見た目を変えたい」要件に強く、クラス付け用のJSを削減できます。

また、デザイナーと共同作業する現場では、状態ごとの見た目をCSS側で完結しやすいため、レビューの往復も減らせます。

応用コード

次は「未入力エラー」「妥当入力」「必須未チェック」を1つのフォーム内で扱う例です。構造が増えるため、必要な実装要素をまとめて示します。

See the Pen css :has() 2 by watanabe (@web-sourcecode) on CodePen.

HTML

HTML
<form class="profile-form" novalidate>
  <label class="field">
    <span class="field__label">メールアドレス</span>
    <input class="field__control" type="email" required>
    <small class="field__help">業務連絡に使うアドレスを入力してください。</small>
  </label>

  <label class="check">
    <input type="checkbox" required>
    <span>利用規約に同意する</span>
  </label>

  <button type="submit">保存</button>
</form>

CSS

CSS
.profile-form {
  display: grid;
  gap: 14px;
}

.field {
  display: grid;
  gap: 6px;
  padding: 12px;
  border: 1px solid #cbd5e1;
  border-radius: 10px;
}

.field:has(.field__control:user-invalid) {
  border-color: #ef4444;
  background: #fff5f5;
}

.field:has(.field__control:user-valid) {
  border-color: #22c55e;
  background: #f0fdf4;
}

.check {
  padding: 10px;
  border: 1px solid #cbd5e1;
  border-radius: 10px;
}

.check:has(input:invalid) {
  border-color: #f59e0b;
  background: #fffbeb;
}

このように :has() を使うと、入力要素の状態を親ラッパーに伝播させるコードを増やさずに済みます。

注意点

:has() は便利ですが、複雑なネスト条件を増やしすぎると読みづらくなります。セレクタは短く保ち、対象クラスを明確にしておくのが安全です。

さらに、ブラウザサポート対象によってはフォールバック方針が必要です。影響が大きい画面では、基本表示が崩れないように「:has() なしでも読めるUI」を先に確保してください。

まとめ

:has() はフォーム状態の見せ方をCSS側へ戻しやすい実践的な機能です。軽い状態表現をJSから切り離せるため、UI改修の速度と保守性を両立しやすくなります。

まずはフォーカスとバリデーション表示から導入し、効果を確認しながら適用範囲を広げる進め方がおすすめです。

ポイント

  • 親要素の装飾をJSなしで制御しやすくなる
  • user-valid / user-invalid と組み合わせると実務で使いやすい
  • セレクタは短く保ち、フォールバックを先に決める

参考リンク

read next