한 줄 요약 - :has()는 부모 요소가 안에 든 자식의 상태(입력이 올바른지, 체크됐는지, 이미지가 있는지)를 보고 스스로 스타일을 바꾸는 CSS 선택자입니다. 예전엔 JS로 해야 했던 일을 CSS 한 줄이 대신해요.
이럴 때 필요해요
지금까지 CSS 선택자는 항상 '위에서 아래로'만 흘렀습니다. 부모는 자식을 고를 수 있어도, 자식의 상태(:valid / :checked 등)를 보고 부모를 바꿀 방법이 없었죠. 그래서 '이메일이 올바르면 칸 전체를 초록으로' 같은 요구는 onChange(값이 바뀔 때 호출되는 이벤트)를 듣고 부모에 클래스를 붙였다 뗐다 하는 JS로만 가능했습니다. :has()는 바로 그 부모-자식 방향을 뒤집어 줍니다.
어떻게 동작하나
부모에 .has-field:has(input:valid) 처럼 :has(자식 조건)을 붙이면, 안의 input이 그 조건일 때만 부모 규칙이 적용됩니다.
이메일 칸은 :valid면 초록, :user-invalid(사용자가 건드린 뒤 틀렸을 때만 켜짐)면 빨강이 되어, 빈 칸부터 빨갛게 뜨는 사고를 막습니다.
체크리스트는 :has(:checked)를 세 번 겹쳐 세 항목이 모두 체크된 경우만 '모두 완료' 색으로 바꿉니다 (개수 세기 없이 CSS만으로).
핵심은 이거예요
부모가 자식의 상태를 본다는 것. 이 한 가지만 이해하면 폼 검증 색, 체크 강조, :has(img) 레이아웃 전환이 전부 같은 원리입니다. JS 이벤트 리스너와 className 토글이 통째로 사라져요.
놓치기 쉬운 것
:valid 대신 :user-invalid를 쓰세요. :invalid는 사용자가 입력하기 전부터 켜져 있어 첫 화면이 빨갛게 떠 보입니다.
:has()는 비용이 있는 선택자라 너무 넓은 범위(예: body:has(...))에 남발하면 느려질 수 있어요. 필요한 부모에만 좁게 거세요.
이런 곳에 써요
회원가입/결제 폼의 실시간 검증 색 (틀린 칸만 빨강, 통과한 칸은 초록)
약관 동의 체크리스트, 할 일 목록의 '모두 완료' 강조, 이미지 유무에 따라 펼쳐지는 카드