CSS :has() — 드디어 온 "부모 선택자"
20년 넘게 CSS에 없던 부모 선택자. :has()는 단순한 부모 선택을 넘어 상태 기반 스타일링의 판을 바꿉니다.
2분 읽기
CSS에서 “자식 상태에 따라 부모를 스타일링”하려면 오랫동안 자바스크립트가 필요했습니다. :has()가 이 20년 묵은 공백을 메웠고, 단순한 부모 선택자 이상입니다.
기본: 자식으로 부모를 선택
/* figcaption을 가진 figure에만 여백 */
figure:has(figcaption) {
margin-block: 2rem;
}
/* 유효하지 않은 입력을 포함한 form을 강조 */
form:has(:invalid) {
border: 1px solid var(--danger);
}
:has(X)는 “X를 후손으로 가진 요소”를 선택합니다. 즉 조건은 안쪽, 스타일 대상은 바깥쪽입니다.
부모 선택을 넘어 — 관계 선택자
:has()는 결합자와 함께 쓰면 형제·상태 조합까지 표현합니다.
/* 체크된 체크박스 바로 뒤의 라벨 */
input:checked + label {
font-weight: 700;
}
/* 뒤에 이미지가 오는 제목의 아래 여백 제거 */
h2:has(+ img) {
margin-bottom: 0;
}
/* 카드 안에 이미지가 있으면 그리드 레이아웃으로 */
.card:has(img) {
display: grid;
grid-template-columns: auto 1fr;
}
JS를 걷어내는 실전 패턴
“자식이 하나도 없을 때” 빈 상태
.list:not(:has(li)) {
display: none;
} /* 항목 없으면 목록 숨김 */
폼 유효성에 따른 제출 버튼 — 예전엔 입력마다 이벤트를 달아 JS로 토글하던 것을, 상태 기반 CSS로 상당 부분 대체할 수 있습니다.
다크모드 없이도 문맥 스타일 — 특정 컴포넌트를 포함한 컨테이너만 다르게.
성능과 지원
- 지원: 모든 최신 브라우저에서 사용 가능합니다. Firefox가 121(2023년 말)에 합류하며 Baseline에 올랐습니다. 아주 구형을 지원해야 하면 폴백을 고려하세요.
- 성능:
:has()는 브라우저가 잘 최적화하지만,*:has(...)처럼 대상이 지나치게 넓거나 복잡한 셀렉터를 남발하면 스타일 계산 비용이 커질 수 있습니다. 선택 대상을 좁게 유지하세요.
:has()는 “상태를 클래스로 승격시켜 JS로 토글”하던 코드의 상당수를 없앱니다. 자식/형제 상태로 스타일이 갈린다면 먼저:has()로 표현되는지 생각해 보세요.