Web/CSS

자바스크립트는 이제 그만! CSS :has() 부모 선택자 실무 활용법

kang2oon 2026. 4. 18. 16:30
728x90
반응형
복잡한 자바스크립트 없이 CSS :has() 선택자만으로 부모 요소를 스타일링하는 방법을 배우세요. 웹 성능을 높이는 조건부 스타일링 실무 레시피 3가지와 브라우저 호환성까지 완벽하게 정리해 드립니다.

자바스크립트 구현 조건부 스타일링 VS CSS :has() 선택자로 구현한 스타일링 성능 비교

 

👋 CSS의 오랜 꿈, '부모'를 선택하다

웹 퍼블리싱을 하면서 "특정 자식 요소가 있을 때만 부모의 스타일을 바꾸고 싶다"는 생각, 한 번쯤 해보셨죠?

과거에는 이를 구현하기 위해 어쩔 수 없이 자바스크립트를 사용하여 클래스를 붙였다 뗐다 해야 했습니다. 하지만 이제 그럴 필요가 없습니다. CSS :has() 선택자라는 강력한 무기가 우리에게 생겼기 때문입니다. 이 글에서는 :has()를 활용해 자바스크립트 의존도를 낮추고 성능을 최적화하는 방법을 상세히 다루겠습니다.


🏗️ CSS의 오랜 숙원, '부모 선택자'의 등장

과거의 CSS는 오직 자식이나 형제 요소만 선택할 수 있었습니다. 부모 요소를 선택하는 기능은 CSS의 오랜 숙원이자 '성배'와도 같았죠. 특정 자식 요소의 상태(예: 체크박스가 체크되었을 때)에 따라 부모의 배경색을 바꾸는 간단한 작업조차 JS의 힘을 빌려야 했고, 이는 코드의 복잡도를 높이고 유지보수를 어렵게 만들었습니다.

Key Point: :has()는 단순히 부모를 선택하는 기능을 넘어, 자식 요소의 '상태'(state)와 '관계'(relationship)를 조건으로 스타일을 결정하는 강력한 도구임을 강조해야 합니다. 이것은 웹 스타일링의 패러다임을 바꾸는 혁신입니다.

 

🛠️ :has() 선택자의 기본 문법과 작동 원리

📄 기본 구조 이해하기

:has() 선택자의 기본적인 사용법은 매우 직관적입니다. 선택자 뒤에 :has()를 붙이고, 괄호 안에 조건을 넣으면 됩니다.

예시 code 설명
section:has(img) 이미지(img)를 포함한 section 요소만 선택합니다.
li:has(> a.active) 활성화된 링크(a.active)를 직계 자식으로 둔 li 요소만 선택합니다.
form:has(input:checked) 체크된 인풋 요소가 있는 form 요소만 선택합니다.

이처럼 :has()는 "이 요소를 가지고 있는가?"라는 질문을 던지고, 그 결과에 따라 부모 요소를 스타일링합니다.

CSS :has() 선택자의 작동원리

 

🧠 논리 연산자와의 결합

:has() 내부에 :not(), :is(), :where() 등을 섞어 복잡한 조건을 만드는 방법 예시.

/* 체크박스가 체크되어 있지 않은 .form-group 요소 선택 */
.form-group:has(input[type="checkbox"]:not(:checked)) {
  background-color: #f9f9f9;
}

/* img 또는 video 요소를 포함한 .card 요소 선택 */
.card:has(:is(img, video)) {
  border-radius: 8px;
}

 

반응형

 

🍳 실무에서 바로 써먹는 :has() 활용 레시피 (Case Study)

🖼️ Case 1: 이미지가 있는 카드 vs 없는 카드

과거에는 이미지가 없는 카드를 위해 .no-image 클래스를 JS로 붙여야 했지만, 이제는 CSS로 자동 조절합니다.

/* 이미지가 있는 카드의 본문 패딩 */
.card:has(img) .card-body {
  padding: 16px;
}

/* 이미지가 없는 카드의 본문 패딩 */
.card:not(:has(img)) .card-body {
  padding: 32px;
}

이렇게 하면 이미지 유무에 따라 텍스트 레이아웃이나 패딩을 자동으로 조절하는 법을 쉽게 구현할 수 있습니다.

 

📝 Case 2: 폼(Form) 요소의 유효성 피드백

Input에 값이 입력되었거나 에러가 발생했을 때 부모 .form-group의 테두리 색상 변경하기.

/* 유효하지 않은 입력이 있을 때 .form-group 스타일링 */
.form-group:has(input:invalid) {
  border-color: red;
}

/* 유효한 입력이 있을 때 .form-group 스타일링 */
.form-group:has(input:valid) {
  border-color: green;
}

 

🎯 Case 3: 내비게이션 메뉴의 하이라이트

하위 메뉴가 열려 있을 때 상위 메뉴의 아이콘 모양 변경하기.

/* 하위 메뉴(ul)가 있을 때 상위 메뉴(li)의 아이콘 */
.nav-item:has(ul) .nav-link::after {
  content: '▼'; /* 열린 아이콘 */
}

/* 하위 메뉴(ul)가 없을 때 상위 메뉴(li)의 아이콘 */
.nav-item:not(:has(ul)) .nav-link::after {
  content: '▶'; /* 닫힌 아이콘 */
}

 

🚀 퍼블리셔가 :has()를 써야 하는 진짜 이유: '성능'

자바스크립트로 DOM을 감시(MutationObserver)하거나 클래스를 뗐다 붙였다 하는 작업이 브라우저의 메인 스레드에 주는 부담을 설명합니다.

Key Point: CSS 엔진이 직접 스타일을 계산하게 함으로써 INP(Interaction to Next Paint) 점수를 개선하고 코드 복잡도를 줄이는 이점 강조.

 

🌐 브라우저 호환성 및 대체 전략 (2026년 기준)

2026년 현재 모든 모던 브라우저에서 지원되지만, 구형 브라우저 대응을 위한 @supports 활용법 제시.

/* :has()를 지원하는 브라우저를 위한 스타일 */
@supports selector(:has(*)) {
  .card:has(img) .card-body {
    /* ... */
  }
}

/* :has()를 지원하지 않는 브라우저를 위한 스타일 (대체) */
@supports not selector(:has(*)) {
  /* ... (JS로 클래스를 붙이는 방식 등을 위한 스타일) ... */
}

 

728x90

 

지금까지 CSS :has() 선택자의 등장 배경부터 기본 문법, 실무 활용 레시피, 그리고 퍼블리셔가 꼭 써야 하는 이유인 성능 최적화까지 자세히 알아봤습니다. :has()는 단순히 부모를 선택하는 것을 넘어, 자식 요소의 상태와 관계를 조건으로 스타일을 결정하는 강력한 도구임을 강조해드렸죠.

이제 실무에서 이미지가 있는 카드를 구분하거나, 폼 유효성 피드백을 줄 때, 내비게이션 하이라이트를 구현할 때 더 이상 복잡한 자바스크립트에 의존하지 마세요. CSS :has()를 적극적으로 활용하여 코드 복잡도를 줄이고, 웹 성능을 향상시키는 데 앞장서시길 바랍니다.

728x90
반응형