CSS의 혁명, :has() 선택자 완벽 가이드: 부모를 선택하는 마법
CSS 역사상 가장 오랫동안 개발자들이 염원해왔던 기능, 바로 "부모 선택자"입니다. JQuery의 .parent()나 .closest()를 쓰지 않고는 불가능했던 그 스타일링이, 드디어 순수 CSS만으로 가능해졌습니다. 주인공은 바로 :has() 의사 클래스(Pseudo-class)입니다. 2024년부터 모든 메이저 브라우저(Chrome, Safari, Firefox, Edge)에서 지원하기 시작한 이 혁명적인 선택자를 3,000자 가이드로 정리해드립니다.
1. :has()란 무엇인가?
MDN 정의에 따르면 :has(), 관계형 의사 클래스입니다. 쉽게 말해 **"인수(괄호 안의 선택자)와 일치하는 요소를 하나라도 포함하고 있는 요소"**를 선택합니다.
/* 자식 중에 img 태그가 있는 article 요소만 선택 */
article:has(img) {
background-color: #f0f9ff;
border: 1px solid #bae6fd;
}
이 코드는 img를 가진 article의 배경색을 바꿉니다. 만약 img가 없다면 스타일은 적용되지 않습니다. 즉, 자식의 상태에 따라 부모의 스타일을 결정할 수 있게 된 것입니다!
2. 실전 활용 패턴 BEST 3
2.1 "카드가 이미지를 가질 때만" 레이아웃 변경하기
블로그 카드 컴포넌트를 만들 때, 썸네일 이미지가 있는 경우와 없는 경우의 레이아웃을 다르게 하고 싶을 때가 많습니다. 예전에는 React에서 hasImage 같은 클래스를 조건부로 붙여줬어야 했습니다.
.card {
display: grid;
grid-template-columns: 1fr; /* 기본: 1단 */
}
/* 이미지를 포함하고 있다면 2단 컬럼으로 변경 */
.card:has(.card-image) {
grid-template-columns: 1fr 1fr;
}
JS 로직 없이 CSS만으로 우아하게 해결됩니다.
2.2 폼 유효성 검사 스타일링 (Form Validation)
입력 필드에 에러가 있을 때, 그 input 뿐만 아니라 이를 감싸고 있는 라벨이나 컨테이너 그룹 전체의 색상을 빨간색으로 바꾸고 싶다면 어떨까요?
/* input이 invalid 상태라면, 그 부모인 .input-group의 테두리를 빨갛게 */
.input-group:has(input:invalid) {
border-color: red;
}
/* 에러 메시지(p.error)가 존재한다면 라벨 색상 변경 */
label:has(+ input + p.error) {
color: red;
}
복잡한 JS 상태 관리 없이도 폼의 시각적 피드백을 풍부하게 만들 수 있습니다.
2.3 이전 형제 선택하기 (Previous Sibling)
CSS에는 '다음 형제(+)'나 '일반 형제(~)' 선택자는 있었지만, '이전 형제'를 선택하는 방법은 없었습니다. :has()를 응용하면 이것이 가능해집니다.
/* 마우스가 호버된 아이템의 '앞에 있는' 아이템 선택 */
.item:has(+ .item:hover) {
transform: scale(0.95);
opacity: 0.7;
}
애플의 독(Dock) 메뉴처럼 마우스를 올린 아이템 주변이 반응하는 인터랙션을 CSS만으로 구현할 수 있습니다.
3. 논리 조합: :not()과 함께 쓰기
:has()는 :not()과 결합했을 때 더욱 강력해집니다.
/* h2 태그를 포함하지 '않은' 섹션만 선택 */
section:not(:has(h2)) {
border: 1px dashed gray; /* 제목 없는 섹션 강조 표시 */
}
이처럼 특정 요소가 '결여된' 상태를 스타일링하는 것은 기존에는 불가능에 가까웠던 작업입니다.
4. 성능 이슈는 없을까?
:has()는 브라우저 렌더링 엔진 입장에서 꽤 비싼 연산입니다. 요소를 렌더링하다가 자식 요소를 확인하기 위해 다시 DOM 트리를 확인해야 하기 때문입니다. 하지만 최신 브라우저 엔진들은 고도로 최적화되어 있어, 일반적인 웹 페이지 규모에서는 성능 저하를 거의 체감할 수 없습니다. 다만 수천 개의 요소가 있는 거대한 리스트에서 복잡한 :has() 연쇄를 사용하는 것은 피하는 것이 좋습니다.
5. 결론: CSS-in-JS의 종말?
:has(), 컨테이너 쿼리(@container), 중첩(Nesting) 등 최근 CSS의 발전 속도는 눈부십니다. 과거 JS로만 처리해야 했던 많은 로직들이 순수 CSS의 영역으로 넘어오고 있습니다.
물론 동적인 상태 관리가 필요한 부분은 여전히 JS가 필요하겠지만, **"스타일은 CSS에게, 로직은 JS에게"**라는 본연의 역할 분담이 더욱 명확해지고 있습니다. 지금 당장 :has()를 써보세요. 제이쿼리의 추억과 함께, 새 세상이 열릴 것입니다.