버튼 하나 눌렀다고 자바스크립트로 부모에 클래스를 붙였다 뗐다 한다 — CSS :has() 부모 선택자가 코드 한 줄로 화면을 반응시키는 시대

웹을 조금이라도 만져본 분이라면 한 번쯤 들어봤을 말이 있습니다. "CSS로는 부모를 못 건드린다." 자식 요소를 고르는 건 쉬운데, 자식의 상태에 따라 부모를 바꾸는 일은 늘 자바스크립트의 몫이었죠. 입력칸이 채워지면 감싼 카드에 테두리를 넣고, 메뉴가 열리면 본문을 흐리게 하고 — 이런 사소한 반응 하나하나에 클래스를 붙였다 뗐다 하는 코드가 따라붙었습니다. 그런데 이 오래된 상식이 :has() 선택자 하나로 통째로 흔들리고 있습니다.

왜 그동안 '부모는 못 건드린다'가 상식이었나

CSS 선택자는 위에서 아래로 흐르도록 설계됐습니다. .card .title처럼 '부모 안의 자식'은 자연스럽게 고를 수 있지만, 반대로 '특정 자식을 가진 부모'를 거슬러 올라가 고르는 문법은 없었습니다. 그래서 개발자들은 화면을 조건에 따라 바꾸려고 자바스크립트로 요소를 감시하고, 이벤트를 걸고, 클래스를 토글하는 코드를 매번 새로 썼습니다. 작은 변화 하나에도 스크립트가 쌓이니, 페이지는 무거워지고 유지보수는 까다로워졌죠.

:has()가 바꾼 것 — 조건을 CSS 한 줄로

:has()는 말 그대로 '~을 가진'을 뜻합니다. .card:has(img)는 '이미지를 품은 카드', label:has(input:checked)는 '체크된 입력칸을 감싼 라벨'을 가리킵니다. 이제 자식의 상태를 보고 부모의 모습을 바꾸는 일이 스크립트 없이 스타일시트 한 줄로 끝납니다.

활용 폭도 넓습니다. 폼 입력값이 비어 있는지, 슬라이드에 사진이 몇 장인지, 특정 영역이 화면에 들어왔는지 같은 조건을 CSS만으로 읽어내 반응시킬 수 있습니다. 흔히 '부모 선택자'로 불리지만, 실제로는 관계를 조건으로 거는 선택자에 가깝습니다.

작은 회사 웹사이트에서 바로 쓰는 장면

  • 문의 폼 검증 표시: 필수 입력칸이 비면 감싼 영역을 붉게, 다 채우면 전송 버튼을 또렷하게 — 스크립트 없이 입력 상태만으로 안내합니다.
  • 메뉴가 열리면 본문 흐리게: 모바일에서 햄버거 메뉴가 펼쳐질 때 뒤 배경을 어둡게 깔아 시선을 모읍니다.
  • 사진 유무에 따른 카드 레이아웃: 썸네일이 있는 글과 없는 글의 간격·정렬을 자동으로 다르게 잡아 목록을 깔끔하게 유지합니다.
  • 빈 장바구니·빈 목록 안내: 항목이 하나도 없을 때만 안내 문구를 띄워, '텅 빈 화면'을 막습니다.

예전 같으면 각각 작은 스크립트가 필요했을 장면들이, 이제 스타일시트 안에서 조건 한 줄로 정리됩니다. 코드가 줄면 페이지가 가벼워지고, 손님 휴대폰에서 화면이 더 빨리, 더 매끄럽게 반응합니다.

도입 전 짚어둘 한 가지

:has()는 2023년 말부터 크롬·사파리·엣지·파이어폭스 최신 버전에서 폭넓게 지원되며, 이제는 일상적으로 써도 무리가 없는 단계에 들어섰습니다. 다만 아주 오래된 브라우저를 쓰는 손님이 적지 않은 업종이라면, :has()가 적용되지 않아도 핵심 기능은 멀쩡히 돌아가도록 기본 화면을 먼저 갖춰두는 '점진적 향상' 방식이 안전합니다. 화면을 더 좋게 만드는 장식으로 얹되, 그것 없이는 못 쓰는 구조로 짜지 않는 것이 요령입니다.

작은 변화가 쌓이면 사이트의 무게가 달라진다

:has() 같은 최신 CSS 기능의 가치는 화려한 효과가 아니라, 그동안 당연히 자바스크립트로 떼우던 일을 더 가볍고 안정적으로 바꾸는 데 있습니다. 이런 작은 선택이 모이면 로딩 속도, 유지보수 비용, 그리고 손님이 느끼는 매끄러움까지 조용히 달라집니다.

CYAN 에이전시는 작은 회사 웹사이트를 만들 때 이런 최신 표준을 실무에 맞게 골라 씁니다. 유행을 좇기보다, 더 빠르고 더 오래가는 사이트를 만드는 데 도움이 되는 기술인지부터 따져보고 적용합니다. 지금 운영 중인 사이트가 무겁고 굼뜨게 느껴진다면, 화면을 떠받치는 코드부터 한번 점검해 보시길 권합니다.