과거에는 콘텐츠에 따라 구성요소에 다른 레이아웃을 적용하려면 자바스크립트 또는 HTML 템플릿 언어의 조건부 로직을 사용하여 수정자 클래스 (예: .card--has-image 또는 .card--text-only)를 삽입해야 했습니다.
:has() 가상 클래스는 상위 선택자 역할을 하여 이러한 필요성을 없앱니다. 이를 통해 특정 하위 요소의 존재 여부에 따라 컨테이너 요소의 스타일을 조건부로 지정할 수 있습니다.
:has()를 사용하면 구성요소의 실제 DOM 콘텐츠를 기반으로 CSS에서 완전히 별개의 레이아웃 변형을 쉽게 정의할 수 있습니다. 원하는 경우 :not()와 결합하여 콘텐츠의 부재를 명시적으로 타겟팅하여 기본 레이아웃을 정의할 수도 있습니다.
콘텐츠 기반 컨테이너 스타일 지정 구현
필수: 컨테이너 요소에서 :has() 선택기를 사용하여 특정 하위 콘텐츠의 존재를 감지해야 합니다.
콘텐츠에 따라 레이아웃이 변경되는 구성요소를 빌드하려면 다음 안내를 따르세요.
- 기본 스타일 정의: 컨테이너 요소에 기본 레이아웃 스타일을 적용합니다 (예: 간단한 단일 열 스택).
- 콘텐츠 기반 재정의 적용:
:has([child-selector])를 사용하여 컨테이너를 타겟팅하고 해당 콘텐츠가 있을 때 새 레이아웃 스타일 (예: 다중 열 그리드)을 적용합니다.
예: 이미지가 있는 경우 나란히 배치되는 레이아웃으로 전환되는 카드 구성요소
/* 1. Define the default state on the component container */
/* This applies when there is NO image */
.article-card {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
border: 1px solid #ccc;
border-radius: 8px;
}
/* 2. Apply styles when a specific child element is present */
/* MANDATORY: Target the container and use :has() to check for the descendant */
.article-card:has(img) {
/* Change the layout to a row if an image exists */
flex-direction: row;
align-items: center;
}
/* Optional: Style the image itself (no :has() needed here) */
.article-card img {
width: 150px;
height: auto;
border-radius: 4px;
}
/* You can also combine :has() and :not() to explicitly target the ABSENCE of content */
/* This selects a card that DOES NOT have an image */
.article-card:not(:has(img)) {
/* E.g., apply a different background color for text-only cards */
background-color: #f9f9f9;
}
<!-- Assume an <h1> precedes these components in the document layout -->
<!-- A card WITH an image (will use row layout) -->
<article class="article-card">
<img src="thumbnail.jpg" alt="Article thumbnail" />
<div class="content">
<h2>Card With Image</h2>
<p>This card lays out its content horizontally.</p>
</div>
</article>
<!-- A card WITHOUT an image (will use default column layout) -->
<article class="article-card">
<div class="content">
<h2>Text-Only Card</h2>
<p>This card lays out its content vertically, and gets its background color from the :not(:has()) rule.</p>
</div>
</article>
성능 도움말: :has()를 사용할 때는 선택기의 범위를 가능한 한 가장 구체적인 구성요소 컨테이너 (예: .article-card)로 지정하세요. 스타일 변경사항이 매우 국지적인 경우 body:has(img)와 같은 매우 높은 수준의 요소에 고정하지 마세요. 광범위한 :has() 쿼리는 더 많은 레이아웃 재계산을 트리거할 수 있습니다.
대체 전략
콘텐츠 기반 레이아웃 스타일 지정이 사용자 환경이나 페이지 디자인에 중요한 경우 :has() 선택기를 지원하지 않는 브라우저에 대한 대체 기능을 제공해야 합니다. 순전히 장식 효과를 위해 :has()를 대체 없이 점진적 개선으로 사용할 수 있습니다.
필수: 중요한 레이아웃에 대한 대체 기능을 구현할 때는 CSS에서 @supports not selector(:has(*))를 사용하여 기존의 클래스 기반 대체 기능 (예: .has-image)을 정의해야 합니다.
상호작용 상태 기반 스타일 지정과 달리 콘텐츠 존재 여부는 일반적으로 렌더링 시점에 알 수 있습니다. 가장 강력한 대체 방법은 데이터에 하위 요소 (예: 이미지)가 있는 경우 서버 측 템플릿 엔진 (또는 정적 사이트 생성기)이 .has-image와 같은 클래스를 컨테이너에 삽입하도록 하는 것입니다.
서버 측 렌더링을 사용할 수 없는 경우 CSS.supports()가 포함된 작은 스크립트를 사용하여 콘텐츠를 감지하고 로드 시 또는 동적 콘텐츠 삽입 후에 클래스를 추가해야 합니다.
/* 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 layout overrides */
.article-card.has-image {
flex-direction: row;
align-items: center;
}
.article-card:not(.has-image) {
background-color: #f9f9f9;
}
}
/* Fallback JavaScript for older browsers (if not using SSR to add the class) */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
// Find all components that need checking
const cards = document.querySelectorAll('.article-card');
cards.forEach(card => {
// If the critical content exists, manually add the fallback class
if (card.querySelector('img')) {
card.classList.add('has-image');
}
});
}