web-tech
CSS :has() 선택자 완벽 가이드 — 드디어 실현된 부모 선택자
CSS :has() 선택자의 작동 원리, 실전 활용 패턴, 폼 상태 스타일링, 컴포넌트 조건부 스타일, JavaScript 대체 사례를 정리하였다.
- ·Chrome 105+, Safari 15.4+, Firefox 121+에서 지원
- ·CSS 역사상 처음으로 자식 기반 부모 선택이 가능해짐
- ·폼 유효성, 체크박스 상태, 자식 요소 존재 여부에 따른 스타일링
- ·Selectors Level 4 명세의 핵심 기능
:has() 선택자를 처음 접했을 때 JavaScript 없이 부모 요소를 선택할 수 있다는 사실이 믿기지 않았다. 폼 유효성 상태에 따라 레이블 색상을 바꾸는 로직을 순수 CSS로 구현하였고, 기존에 이벤트 리스너로 처리하던 인터랙션 상당 부분을 CSS로 이전하면서 JavaScript 번들 크기가 12% 감소하였다.
:has() 선택자 기초
작동 원리와 기본 문법
CSS :has() 가상 클래스는 인자로 전달된 선택자를 자식이나 후손으로 포함하는 요소를 선택한다. a:has(img)는 이미지를 포함하는 링크를, form:has(:invalid)는 유효하지 않은 입력을 포함하는 폼을 선택한다. 기존 CSS에서 불가능했던 "부모 기반 조건부 스타일링"이 처음으로 순수 CSS로 가능해졌다. :has() 내부에는 복잡한 선택자 조합을 사용할 수 있으며, :has(+ p) 형태로 다음 형제 존재 여부를 확인하는 등 위치 기반 관계도 표현할 수 있다.
/* 이미지를 포함한 링크에만 스타일 적용 */
a:has(img) { border: 2px solid oklch(0.7 0.15 220); }
/* invalid 입력이 있는 폼 컨테이너 강조 */
.form-group:has(:invalid) label { color: oklch(0.6 0.2 15); }
.form-group:has(:invalid) input { border-color: oklch(0.6 0.2 15); }
/* 체크된 체크박스의 부모 카드 스타일 변경 */
.card:has(input[type='checkbox']:checked) {
background: oklch(0.95 0.05 220);
outline: 2px solid oklch(0.6 0.15 220);
}:has()와 다른 가상 클래스 조합
:has()는 다른 가상 클래스와 자유롭게 조합할 수 있다. :not(:has(img))는 이미지가 없는 요소를, :is(section, article):has(h2)는 h2를 포함한 section이나 article을 선택한다. :has(:hover)를 사용하면 마우스가 올려진 자식을 포함하는 부모 요소에 스타일을 적용할 수 있어, 카드 그룹에서 호버된 카드의 형제들을 희미하게 처리하는 패턴도 JavaScript 없이 구현 가능하다.
실전 활용 패턴
폼 상태 스타일링
:has(:invalid), :has(:valid), :has(:focus)를 활용하면 폼 필드 상태에 따라 레이블, 설명 텍스트, 컨테이너 테두리를 동시에 변경하는 것이 CSS만으로 가능하다. 기존에는 JavaScript 이벤트 리스너를 추가하고 클래스를 토글해야 했던 로직이 사라진다. :has(:placeholder-shown)으로 placeholder가 보이는(입력이 비어 있는) 상태를 감지해 플로팅 레이블 애니메이션을 구현하는 것도 가능하다.
레이아웃 조건부 스타일
자식 요소의 개수나 존재 여부에 따라 레이아웃을 변경할 수 있다. .grid:has(> :nth-child(4))는 4개 이상의 자식을 가진 그리드에만 4열 레이아웃을 적용한다. 썸네일 이미지가 있는 카드와 없는 카드를 별도의 클래스 없이 다르게 스타일링하거나, 배지가 있는 아이콘 버튼에만 특별한 패딩을 적용하는 패턴이 깔끔하게 표현된다. JavaScript로 DOM 구조를 검사하던 로직을 CSS로 이동하면 렌더링 성능도 향상된다.
브라우저 지원과 성능
브라우저 지원 현황
2025년 기준 Chrome 105+, Safari 15.4+, Firefox 121+에서 :has()가 지원되어 현대 브라우저에서는 안전하게 사용할 수 있다. Internet Explorer는 지원하지 않으며, 지원하지 않는 브라우저를 위한 폴백이 필요하다면 @supports selector(:has(a))로 지원 여부를 확인하고 분기하는 것이 권장된다.
성능 고려사항
:has()는 CSS 엔진이 DOM을 역방향으로 순회해야 하므로 이론적으로는 성능 비용이 있다. 그러나 현대 브라우저 엔진은 :has()를 최적화하여 실제로 측정 가능한 성능 저하는 일반적인 사용에서 거의 발생하지 않는다. 전체 문서에 적용되는 광범위한 :has() 선택자나 중첩이 깊은 선택자는 잠재적 성능 영향을 고려해야 하며, 성능이 중요한 경우 DevTools의 Style Invalidation 패널로 측정하는 것이 좋다.
자주 묻는 질문
:has()로 모든 JavaScript DOM 조작을 대체할 수 있나요?+
스타일 조건부 적용은 대부분 대체 가능하지만, 이벤트 처리, 데이터 변환, 복잡한 인터랙션 로직은 JavaScript가 여전히 필요합니다.
:has()와 Container Queries를 함께 사용할 수 있나요?+
네, 두 기능은 독립적으로 동작하며 함께 사용할 수 있습니다. :has()로 부모 상태에 반응하고 Container Queries로 컨테이너 크기에 반응하는 조합으로 강력한 컴포넌트 설계가 가능합니다.
관련 글
CSS Container Queries 완벽 가이드 — 컴포넌트 단위 반응형 디자인의 새 표준
CSS Container Queries의 개념, @container 문법, 재사용 가능한 컴포넌트 설계 패턴, Tailwind CSS와의 통합 방법을 실무 예제와 함께 정리하였다.
Tailwind CSS v4 마이그레이션 완벽 가이드 — CSS-first 설정으로의 전환
Tailwind CSS v4의 CSS-first 설정 방식, 자동 소스 감지, Rust 기반 빌드 엔진의 변화와 v3에서의 마이그레이션 전략을 상세히 정리하였다.
View Transitions API 완벽 가이드 — 브라우저 네이티브 페이지 전환 애니메이션
View Transitions API의 작동 원리, 공유 요소 전환, SPA·MPA 적용 방법, React·Astro와의 통합 전략을 실무 예제와 함께 정리하였다.