CSSのhas()セレクタを使うことで、JavaScriptなしでフォームバリデーションやカードレイアウトの動的な切り替えができるようになりました。今回は実務でよく使える実践的な使い方を解説します。

基本的なhas()セレクタの書き方

has()セレクタは親要素が特定の子要素を持っているかどうかで、親要素にスタイルを適用できる便利な機能です。基本的な書き方は以下の通りです。

/* 基本的な書き方 */
.parent:has(.child) {
  /* .childクラスを持つ子要素がある.parentに適用 */
  background-color: #f0f0f0;
}

/* チェックボックスがチェックされた時 */
.form-group:has(input:checked) {
  background-color: #e8f5e8;
}

実装例1: フォームバリデーション表示

入力値に応じてフォームグループの見た目を変更する実装です。エラーメッセージの表示・非表示やスタイル変更がCSSだけで可能になります。

HTML

<form class="form">
  <div class="form-group">
    <label for="email">メールアドレス</label>
    <input type="email" id="email" name="email" required>
    <span class="error-message">有効なメールアドレスを入力してください</span>
  </div>
  
  <div class="form-group">
    <label for="password">パスワード</label>
    <input type="password" id="password" name="password" minlength="8" required>
    <span class="error-message">8文字以上で入力してください</span>
  </div>
  
  <div class="form-group">
    <label>
      <input type="checkbox" name="agree" required>
      利用規約に同意する
    </label>
  </div>
  
  <button type="submit">送信</button>
</form>

CSS

.form-group {
  margin-bottom: 1rem;
  padding: 1rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  transition: all 0.3s ease;
}

/* エラー状態のスタイル */
.form-group:has(input:invalid) {
  border-color: #e74c3c;
  background-color: #fdf2f2;
}

/* 正常状態のスタイル */
.form-group:has(input:valid) {
  border-color: #27ae60;
  background-color: #f2fdf2;
}

/* エラーメッセージの制御 */
.error-message {
  display: none;
  color: #e74c3c;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

/* 無効な入力時にエラーメッセージを表示 */
.form-group:has(input:invalid) .error-message {
  display: block;
}

/* チェックボックス同意時のボタンスタイル */
.form:has(input[name="agree"]:checked) button {
  background-color: #3498db;
  cursor: pointer;
}

.form:has(input[name="agree"]:not(:checked)) button {
  background-color: #bdc3c7;
  cursor: not-allowed;
}

実装例2: カード内画像有無での切り替え

カードコンポーネントで画像がある場合とない場合でレイアウトを自動切り替えする実装です。CMSでの記事一覧などで重宝します。

HTML

<div class="card-list">
  <!-- 画像ありのカード -->
  <article class="card">
    <img src="image1.jpg" alt="記事画像" class="card-image">
    <div class="card-content">
      <h3 class="card-title">画像付きの記事タイトル</h3>
      <p class="card-text">記事の概要テキストがここに入ります。</p>
      <time class="card-date">2024-01-15</time>
    </div>
  </article>
  
  <!-- 画像なしのカード -->
  <article class="card">
    <div class="card-content">
      <h3 class="card-title">画像なしの記事タイトル</h3>
      <p class="card-text">こちらは画像がない記事の例です。レイアウトが自動で調整されます。</p>
      <time class="card-date">2024-01-10</time>
    </div>
  </article>
</div>

CSS

.card-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
}

.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
  background: white;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  transition: transform 0.3s ease;
}

.card:hover {
  transform: translateY(-2px);
}

/* 画像がある場合のカードレイアウト */
.card:has(.card-image) {
  display: flex;
  flex-direction: column;
}

.card:has(.card-image) .card-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.card:has(.card-image) .card-content {
  padding: 1.5rem;
  flex: 1;
}

/* 画像がない場合のカードレイアウト */
.card:not(:has(.card-image)) {
  padding: 2rem;
  min-height: 200px;
  display: flex;
  align-items: center;
}

.card:not(:has(.card-image)) .card-content {
  width: 100%;
}

/* 画像なしカードのタイトル強調 */
.card:not(:has(.card-image)) .card-title {
  font-size: 1.5rem;
  margin-bottom: 1rem;
  color: #2c3e50;
}

/* 共通スタイル */
.card-title {
  margin: 0 0 0.75rem 0;
  font-size: 1.25rem;
  color: #333;
}

.card-text {
  margin: 0 0 1rem 0;
  color: #666;
  line-height: 1.6;
}

.card-date {
  color: #999;
  font-size: 0.875rem;
}

実装例3: ナビゲーション状態管理

チェックボックスの状態でモバイルメニューの開閉を制御する実装です。

<nav class="nav">
  <input type="checkbox" id="menu-toggle" class="menu-toggle">
  <label for="menu-toggle" class="menu-button">☰</label>
  
  <ul class="nav-list">
    <li><a href="#">ホーム</a></li>
    <li><a href="#">サービス</a></li>
    <li><a href="#">会社概要</a></li>
    <li><a href="#">お問い合わせ</a></li>
  </ul>
</nav>
/* メニューが開いている時のナビゲーション背景 */
.nav:has(.menu-toggle:checked) {
  background-color: rgba(0,0,0,0.9);
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
}

/* メニューリストの制御 */
.nav-list {
  display: none;
  flex-direction: column;
  padding: 2rem;
}

.nav:has(.menu-toggle:checked) .nav-list {
  display: flex;
}

ブラウザ対応と注意点

has()セレクタは比較的新しい機能のため、以下の点に注意が必要です。

  • Chrome 105+、Firefox 103+、Safari 15.4+で対応
  • IE、古いブラウザでは動作しません
  • フォールバック用のスタイルも用意しましょう
  • パフォーマンスを考慮し、深い入れ子は避けましょう
/* フォールバック例 */
@supports not selector(:has(*)) {
  /* has()非対応ブラウザ用のスタイル */
  .form-group {
    border-color: #ddd;
  }
  
  .error-message {
    display: block; /* 常に表示 */
  }
}

まとめ

CSSのhas()セレクタを活用することで、JavaScriptを使わずに動的なUIが実現できます。フォームバリデーション、カードレイアウト、ナビゲーション制御など、実務でよく遭遇するケースに応用できるため、ぜひ実際のプロジェクトで試してみてください。ブラウザ対応状況を確認しつつ、適切なフォールバックも忘れずに実装しましょう。