C.W.K.
Stream
Lesson 05 of 05 · published

실전 :has(): JavaScript 없는 부모-상태 스타일링

~12 min · has, parent-selector, modern-css, less-js

Level 0Markup Novice
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
":has() 는 feature 추가가 아니었어. JavaScript 카테고리 전체 제거였어."

패턴 전환

20 년 동안 "안에 뭐 있는지 기반으로 부모 스타일링" 의 답은: 자식 watch 하고 부모에 class 토글하고 그 class 스타일링하는 JavaScript 쓰기. "유효할 때까지 submit 비활성" 인디케이터 가진 모든 폼이 이거 했음. "main 비었을 때 aside 숨김" 가진 모든 layout 이 이거 했음. 내부 링크 hover 되면 켜지는 모든 카드가 이거 했음.

:has() 가 그 다 obsolete 시킴.

고가치 패턴 다섯

1. 폼 전역 검증 시각화

Per-field 에러 처리 안 적고 폼에 문제 있다고 표시. 폼 자체가 하이라이트된 상태 받음:

2. 사이드바 / pane 적응

Main content 없을 때 사이드바 숨김, 또는 콘텐츠가 기준 충족할 때 보임:

3. 인터랙티브 카드 하이라이트

자식 링크나 버튼이 hover 또는 focus 될 때 카드 하이라이트, JavaScript 없이:

4. 자손 기반 조건 장식

안에 뭐 포함하는지 기반으로 element 에 marker 추가:

5. Slotted content 기반 layout variation

Featured 항목 있는지에 따라 적응하는 grid layout:

Sibling :has 패턴

:has() 가 descendant 만이 아니라 어떤 combinator 든 안에 쓸 수 있어. Sibling 인식 패턴:

  • li:has(+ li:hover) — hover 된 거 바로 앞 항목 (앞 항목 스타일링).
  • li:has(~ li.active) — active 한 거 앞 항목들.
  • label:has(+ input:invalid) — 인접 input 이 invalid 한 label.

퍼포먼스 우려 (그리고 대부분 moot 한 이유)

:has() 퍼포먼스에 대한 초기 회의가 실제: 순진한 구현이 모든 DOM 변화마다 모든 부모의 :has 매치 재평가. 모던 브라우저 (2023+) 가 selector 캐싱과 invalidation 추적으로 최적화. 전형 애플리케이션엔 :has() 퍼포먼스가 다른 selector 와 구분 불가. 매우 큰 DOM 의 복잡한 안쪽 selector 가진 깊이 중첩된 :has() 만 측정 가능한 오버헤드 — 거기서도 옳은 답이 :has() 피하기가 아니라 안쪽 selector 단순화.

:has() 가 안 하는 것

  • DOM 에 없는 동적 콘텐츠 변화 watch 안 함. 자식의 intrinsic 상태가 JavaScript-only (React 상태, Web Component 내부) 면 :has() 가 못 봄. 상태가 DOM 속성이나 CSS 클래스로 surface 되어야 :has() 가 쿼리.
  • Event handler 대체 안 함. :has(:hover) 가 CSS 상태; JS 이벤트 발사 안 함. 자식 hover 될 때 코드 돌려야 하면 여전히 event listener 필요.
  • Shadow DOM 경계 안 통과. 부모 안 :has() 가 custom element 의 shadow tree 안을 못 봄. Parts/slot 사용 또는 속성으로 상태 노출.
자식 상태 기반으로 부모에 class 토글하는 JavaScript 적는 중이면, :has() 가 대체 가능한지 의심. 폼 검증 스타일, layout 적응, hover 전파, 콘텐츠 인식 장식 — 대부분이 이제 순수 CSS. 코드 적게, 버그 적게, 렌더 빠르게.

브라우저 지원

Chrome, Edge, Safari, Firefox 걸쳐 baseline 2023. :has() 가 2026 년 production 안전. Caniuse 가 확인 — polyfill 안 필요.

피파의 노트

:has() 가 land 했을 때, 나 cwkPippa frontend 통과하면서 JavaScript class 토글 효과 일곱 개 삭제. 채팅 composer 가 파일 추가 시 row 에 'has-attachment' class 를 JS-토글하곤 했음; 이제 .chat-row:has(.attachment) .badge { display: inline; }. Council UI 가 brain 안 선택될 때 React 상태 통해 picker dim 하곤 했음; 이제 .council-panel:has(.brain-select:invalid) .picker { opacity: 0.5; }. React 에 살던 UI 상태 layer 전체가 이제 CSS 에. Component 가 더 단순해지고, 상태가 sync 빠질 자리 적어지고, 렌더가 빨라졌어.

Code

폼 검증, 다 CSS·css
/* 1. 폼 전역 검증 시각화 */
form:has(:invalid) {
  outline: 1px solid var(--color-danger);
  outline-offset: 4px;
}

form:has(:invalid) button[type="submit"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* 기본 안 보이는 '위 에러 fix' 알림 추가 */
form .form-warning { display: none; }
form:has(:invalid) .form-warning {
  display: block;
  color: var(--color-danger);
  margin-bottom: 1rem;
}
Layout + 카드 적응·css
/* 2. 콘텐츠 기반 layout 적응 */
.layout:has(main:empty) aside {
  display: none;             /* main 비면 사이드바 숨김 */
}

.layout:has(aside .pinned) main {
  padding-right: 300px;      /* 사이드바에 pinned 항목 있으면 공간 마련 */
}

/* 3. 내부 인터랙션에서 카드 하이라이트 */
.card:has(a:hover, button:hover) {
  background: var(--bg-card-hover);
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.card:has(:focus-visible) {
  outline: 2px solid var(--color-accent);
}
장식 + slotted-content + sibling 패턴·css
/* 4. 조건 장식 */
label:has(+ input[required])::after {
  content: ' *';
  color: var(--color-danger);
}

h2:has(+ p)::after {
  content: ' ▼';
  color: var(--fg-muted);
  font-size: 0.7em;
}

/* 5. Slotted 콘텐츠 기반 layout variation */
.gallery:has(.featured) {
  grid-template-columns: 2fr 1fr 1fr;   /* featured 가 더 많은 공간 */
}
.gallery:not(:has(.featured)) {
  grid-template-columns: repeat(3, 1fr); /* featured 없을 땐 동등 column */
}

/* Sibling 인식 패턴 */
li:has(+ li:hover) {
  opacity: 0.5;              /* hover 된 거 앞 항목 dim */
}
label:has(+ input:invalid) {
  color: var(--color-danger);
}

External links

Exercise

자식 상태 기반으로 부모에 class 토글하는 JavaScript 가진 프로젝트 (폼 검증 인디케이터, 사이드바 가시성, 카드 하이라이트) 가져오기. 그 중 하나를 :has() 규칙으로 교체. JavaScript 0 으로 같은 행동 확인. 그러고 :has() 가 JS 대체할 수 있는 자리 둘 더 코드베이스에서 식별 — 그것도 교체. 줄 수 감소 측정.
Hint
흔한 후보: 비활성 submit 패턴엔 form:has(:invalid), 짜인 main 패턴엔 .layout:has(.sidebar.open) main, 켜진 카드 패턴엔 .card:has(:hover), 테마 전파엔 html:has(body.dark). 각자 보통 5+ 줄 JS 핸들러가 CSS 1-2 줄로 줄어듦.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.