Contents
see List개요
CSS는 2024-2025년 사이에 놀라운 진화를 이루었습니다. Container Queries와 :has() 선택자는 더 이상 실험적 기능이 아닌 모든 주요 브라우저에서 지원되는 표준입니다. 이 두 기능은 반응형 디자인과 컴포넌트 기반 스타일링의 패러다임을 완전히 바꾸고 있습니다. Media Query에만 의존하던 시대를 넘어, 진정한 컴포넌트 중심 CSS 설계가 가능해졌습니다.
핵심 개념
Container Queries는 뷰포트가 아닌 부모 컨테이너의 크기에 반응하는 쿼리입니다. 동일한 컴포넌트가 사이드바에 있을 때와 메인 영역에 있을 때 다른 레이아웃을 자동으로 적용할 수 있습니다.
:has() 선택자는 "부모 선택자"로 불리며, 자식 또는 후손 요소의 존재 여부에 따라 부모 스타일을 변경할 수 있습니다. 이전에는 JavaScript로만 가능했던 패턴이 이제 순수 CSS로 구현됩니다.
Container Query Units인 cqw, cqh, cqi, cqb는 컨테이너 크기에 상대적인 단위로, vw/vh의 컨테이너 버전입니다.
실전 예제
Container Queries로 반응형 카드 컴포넌트를 구현합니다.
/* 카드 컨테이너를 Container로 선언 */
.card-container {
container-type: inline-size;
container-name: card;
}
.card {
display: grid;
gap: 1rem;
padding: 1rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
/* 컨테이너가 400px 미만일 때 세로 레이아웃 */
@container card (width < 400px) {
.card {
grid-template-columns: 1fr;
}
.card__image {
aspect-ratio: 16/9;
}
.card__title {
font-size: 1rem;
}
}
/* 컨테이너가 400px 이상일 때 가로 레이아웃 */
@container card (width >= 400px) {
.card {
grid-template-columns: 200px 1fr;
}
.card__image {
aspect-ratio: 1/1;
}
.card__title {
font-size: 1.5rem;
}
}
/* Container Query Units 활용 */
@container card (width >= 600px) {
.card__title {
font-size: calc(3cqw + 1rem); /* 컨테이너 너비의 3% + 1rem */
}
}
:has() 선택자로 상태 기반 스타일링을 구현합니다.
/* 체크된 항목을 포함한 리스트 스타일 변경 */
.todo-list:has(input[type="checkbox"]:checked) {
background-color: #f0f9ff;
border-left: 4px solid #0284c7;
}
/* 이미지가 있는 아티클만 그리드 레이아웃 */
article:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
gap: 2rem;
}
/* 에러 메시지를 포함한 폼 필드 하이라이트 */
.form-field:has(.error-message) {
border-color: #dc2626;
background-color: #fef2f2;
}
.form-field:has(input:focus) {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* 빈 요소 숨기기 - :empty와 결합 */
.notification-badge:has(:empty) {
display: none;
}
/* 특정 자식이 없을 때만 스타일 적용 */
.card:not(:has(.card__footer)) {
padding-bottom: 0;
}
Container Queries와 :has()를 조합한 고급 패턴입니다.
<div class="product-grid">
<div class="product-card-wrapper">
<div class="product-card">
<img src="product.jpg" alt="Product">
<h3>Product Name</h3>
<p class="price">$99</p>
<div class="badge sale">SALE</div>
</div>
</div>
</div>
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
}
.product-card-wrapper {
container-type: inline-size;
}
.product-card {
position: relative;
padding: 1rem;
background: white;
border-radius: 8px;
}
/* 세일 뱃지가 있으면 카드 전체 강조 */
.product-card:has(.badge.sale) {
border: 2px solid #dc2626;
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.15);
}
/* 컨테이너 크기에 따라 이미지 크기 조정 */
@container (width < 300px) {
.product-card img {
width: 100%;
aspect-ratio: 1/1;
}
.product-card h3 {
font-size: 0.875rem;
}
}
@container (width >= 300px) {
.product-card img {
width: 100%;
aspect-ratio: 4/3;
}
.product-card h3 {
font-size: 1.125rem;
}
}
활용 팁
- 브라우저 지원: Container Queries는 Chrome 105+, Safari 16+, Firefox 110+에서 지원됩니다. :has()는 Chrome 105+, Safari 15.4+, Firefox 121+에서 사용 가능합니다.
- 성능: :has()는 복잡한 선택자와 결합 시 성능 영향이 있을 수 있으므로, 깊은 중첩은 피하세요.
- Container Type:
inline-size는 너비만,size는 너비와 높이 모두 쿼리 가능하지만 size는 자식의 높이가 부모에 영향을 주지 못합니다. - Fallback: @supports 쿼리로 폴백 스타일을 제공하세요.
@supports not (container-type: inline-size) - 디버깅: Chrome DevTools의 Styles 패널에서 컨테이너 쿼리 활성 상태를 실시간으로 확인할 수 있습니다.
마무리
Container Queries와 :has() 선택자는 CSS의 표현력을 JavaScript 수준으로 끌어올렸습니다. 컴포넌트 중심 설계가 일반화된 현대 웹 개발에서, 이 두 기능은 더 이상 선택이 아닌 필수입니다. Media Query만으로는 불가능했던 진정한 재사용 가능한 컴포넌트 스타일링이 이제 가능해졌습니다.