개요

CSS :has() 선택자는 "부모 선택자"라고 불리며, 특정 자식이나 후손 요소를 포함하는 부모 요소를 선택할 수 있게 해줍니다. 2024년부터 모든 주요 브라우저에서 완벽하게 지원되며, 기존에 JavaScript로만 가능하던 다양한 인터랙션 패턴을 순수 CSS만으로 구현할 수 있습니다.

이 선택자의 등장으로 CSS의 표현력이 획기적으로 확장되었으며, 조건부 스타일링, 폼 유효성 시각화, 동적 레이아웃 변경 등 실용적인 활용 사례가 매우 다양합니다.

핵심 개념

:has()는 관계형 의사 클래스(relational pseudo-class)로, 괄호 안의 선택자와 일치하는 요소를 포함하는 요소를 선택합니다.

  • 기본 문법: A:has(B)는 B를 후손으로 가진 A를 선택합니다.
  • 직계 자식: A:has(> B)는 B를 직계 자식으로 가진 A를 선택합니다.
  • 인접 형제 조합: A:has(+ B)는 바로 다음에 B가 오는 A를 선택합니다.
  • 부정 조합: A:has(:not(B))는 B가 아닌 후손을 가진 A를 선택합니다.
  • 상태 기반: A:has(input:checked)는 체크된 input을 포함하는 A를 선택합니다.

핵심은 :has()가 "상향 선택"을 가능하게 한다는 점입니다. CSS 역사상 처음으로 자식의 상태에 따라 부모를 스타일링할 수 있게 되었습니다.

실전 예제

JavaScript 없이 토글 카드를 구현하는 예제입니다. 체크박스의 상태에 따라 카드 전체의 스타일이 변합니다.

<div class="toggle-card">
  <label class="toggle-card__header">
    <input type="checkbox" class="toggle-card__input">
    <span class="toggle-card__title">자세히 보기</span>
    <span class="toggle-card__icon"></span>
  </label>
  <div class="toggle-card__content">
    <p>숨겨진 콘텐츠가 여기에 표시됩니다.</p>
  </div>
</div>
/* 체크박스 숨기기 */
.toggle-card__input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* 기본 상태 */
.toggle-card {
  border: 2px solid #e2e8f0;
  border-radius: 8px;
  overflow: hidden;
  transition: border-color 0.3s ease;
}

.toggle-card__content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.4s ease, padding 0.4s ease;
  padding: 0 1.5rem;
}

.toggle-card__icon::after {
  content: "+";
  transition: transform 0.3s ease;
  display: inline-block;
}

/* :has()로 체크 상태 감지 - 부모 카드 스타일 변경 */
.toggle-card:has(.toggle-card__input:checked) {
  border-color: #3b82f6;
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}

.toggle-card:has(.toggle-card__input:checked) .toggle-card__content {
  max-height: 500px;
  padding: 1rem 1.5rem;
}

.toggle-card:has(.toggle-card__input:checked) .toggle-card__icon::after {
  transform: rotate(45deg);
}

/* 폼 유효성 시각화 */
.form-group:has(input:invalid:not(:placeholder-shown)) {
  --field-color: #ef4444;
}

.form-group:has(input:valid:not(:placeholder-shown)) {
  --field-color: #22c55e;
}

활용 팁

  • 폼 유효성 피드백: form:has(:invalid)로 폼 내에 유효하지 않은 필드가 있을 때 제출 버튼 스타일을 변경할 수 있습니다.
  • 빈 상태 처리: ul:has(li)ul:not(:has(li))를 활용하면 목록이 비었을 때 다른 UI를 보여줄 수 있습니다.
  • 다크 모드 토글: body:has(#dark-mode:checked)와 같이 전역 테마 전환도 CSS만으로 가능합니다.
  • 성능 고려: :has()는 브라우저가 최적화하고 있지만, 매우 복잡한 선택자 조합은 피하는 것이 좋습니다.
  • 점진적 향상: @supports selector(:has(*)) { }로 지원 여부를 확인하고 적용하세요.

마무리

CSS :has() 선택자는 웹 개발의 패러다임을 바꾸는 강력한 기능입니다. 간단한 토글부터 복잡한 조건부 레이아웃까지, JavaScript 의존도를 크게 줄이면서 성능과 접근성을 동시에 개선할 수 있습니다. CSS-only 인터랙션의 새 시대를 열어보시기 바랍니다.