하위 상태 기반 스타일 지정

웹 기능: :has() :not()

과거에는 CSS 선택자가 아래로만 이동할 수 있었습니다. 즉, 상위 요소를 기반으로 하위 요소의 스타일을 지정할 수 있었지만 하위 요소를 기반으로 상위 요소의 스타일을 지정할 수는 없었습니다. :has() 가상 클래스는 이를 변경하여 하위 요소의 존재 여부 또는 상태에 따라 컨테이너 요소의 스타일을 조건부로 지정할 수 있습니다.

:has()를 상태 의사 클래스 (예: :checked, :focus, :valid, :invalid, :not())와 결합하면 상위 요소에서 '수정자' 클래스 (예: .is-active 또는 .has-error)를 전환하기 위해 JavaScript를 사용하지 않고도 CSS에서 완전히 복잡한 대화형 UI 구성요소를 빌드할 수 있습니다.

이는 체크박스에 반응하는 현지화된 테마 전환 (:checked), 오류를 강조 표시하는 양식 그룹 (:invalid), 하위 링크에 포커스가 있을 때 카드가 올라가는 경우 (:focus-within)와 같이 내부 상호작용에 응답해야 하는 구성요소에 특히 유용합니다.

상태 기반 컨테이너 스타일 지정 구현

필수: 컨테이너 요소에서 :has() 선택자를 사용하여 상호작용 하위 요소의 특정 상태 (예: :checked, :focus, :invalid)를 감지해야 합니다.

하위 요소의 상태에 따라 스타일을 변경하는 구성요소를 빌드하려면 다음 단계를 따르세요.

  1. 기본 스타일 지정 정의: 컨테이너에 CSS 변수를 설정하여 기본 상태를 정의합니다.
  2. 상태 기반 재정의 적용: :has([child-selector]:[state])를 사용하여 컨테이너를 타겟팅하고 활성 또는 대체 상태의 CSS 변수를 재정의합니다.

예: 하위 전환 버튼에 따라 테마를 변경하는 구성요소

/* 1. Define the default state on the component container */
.theme-card {
  /* Using custom properties makes state-switching cleaner */
  --card-bg: #ffffff;
  --card-text: #333333;
  --card-border: #cccccc;

  background-color: var(--card-bg);
  color: var(--card-text);
  border: 1px solid var(--card-border);

  /* Use a transition for smooth state changes */
  transition: background-color 0.3s, color 0.3s;
}

/* 2. Apply styles when the child enters the specific state */
/* MANDATORY: Target the container and use :has() to check the descendant's state */
.theme-card:has(.theme-toggle:checked) {
  /* Override the properties for the active or alternate state */
  --card-bg: #222222;
  --card-text: #f0f0f0;
  --card-border: #555555;
}
/* You can also combine :has() and :not() to target specific negative states */
/* This selects a card that DOES NOT have a toggle in the checked state */
.theme-card:not(:has(.theme-toggle:checked)) {
  /* Optional: Explicit styles for the unchecked state if needed */
}
<!-- The container element that receives the styling -->
<div class="theme-card">
  <!-- The child element whose state controls the parent -->
  <label>
    <input type="checkbox" class="theme-toggle">
    Enable Dark Mode
  </label>

  <h2>Card Title</h2>
  <p>The style of this entire card is controlled by the checkbox above.</p>
</div>

성능 팁: :has()를 사용할 때는 선택기의 범위를 가능한 한 가장 구체적인 컨테이너 (예: .theme-card)로 지정하세요. 스타일 변경사항이 현지화된 경우 body:has(...)와 같은 매우 높은 수준의 요소에 고정하지 마세요. 광범위한 :has() 쿼리는 더 많은 레이아웃 재계산을 트리거할 수 있기 때문입니다.

대체 전략

상태 기반 스타일 지정이 사용자 환경이나 페이지 레이아웃에 중요한 경우 :has() 선택기를 지원하지 않는 브라우저에 대체 기능을 제공해야 합니다. 순전히 장식 효과를 위해 :has()를 대체 없이 점진적 개선으로 사용할 수 있습니다.

필수: 중요한 기능의 대체 기능을 구현할 때는 CSS에서 @supports not selector(:has(*))를 사용하여 기존의 클래스 기반 대체 기능을 정의해야 합니다. 중요한 상태 변경이 사용자 상호작용에 의존하는 경우 CSS.supports()가 포함된 작은 인라인 스크립트를 사용하여 상태 변경을 나타내는 동등한 JavaScript 이벤트 (예: change, focus, blur)를 기반으로 해당 클래스를 전환해야 합니다.

/* Fallback CSS for older browsers */
/* We check if the browser DOES NOT support the :has() selector */
@supports not selector(:has(*)) {
  /* Define a traditional modifier class that applies the exact same overrides */
  .theme-card.is-active {
    --card-bg: #222222;
    --card-text: #f0f0f0;
    --card-border: #555555;
  }
}
/* Fallback JavaScript for older browsers */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
  const toggle = document.querySelector('.theme-toggle');
  const card = document.querySelector('.theme-card');

  if (toggle && card) {
    // Manually toggle the fallback class when the input state changes
    toggle.addEventListener('change', (e) => {
      card.classList.toggle('is-active', e.target.checked);
    });
  }
}