"20 년 동안 CSS 가 같은 selector 를 세 번 쓰라고 시켰어. 그러더니 함수 네 개가 와서 그 의식의 절반을 조용히 은퇴시켰어."
:is() 가 해결한 문제
:is() 없으면 이렇게 적었어:
article h1, article h2, article h3,
section h1, section h2, section h3,
aside h1, aside h2, aside h3 {
font-family: 'Display', serif;
}
Selector 9 개, 다 "콘텐츠 섹션 안의 어떤 heading". 실수 하나, 9 번 반복. :is() 로:
:is(article, section, aside) :is(h1, h2, h3) {
font-family: 'Display', serif;
}
같은 결과. 9 개 조합 대신 두 그룹. 교차곱이 단일 가독 표현으로 무너져.
:is() Specificity 세부
:is() 가 가장 specific 한 argument 의 specificity 받음. :is(.btn, #cta) 는 (0,1,0,0) — #cta 가 가장 높아서. Specificity 가 평균될 거라 기대한 작성자를 가끔 놀라게 해. 규칙은 간단: 최대, 평균이 아님.
그 escalation 안 원할 때 — 모든 argument 가 zero specificity 기여하길 원할 때 — :where() 써.
:where() — Specificity 지우개
:where(selectors) 가 :is(selectors) 와 같은 element 매치하지만 안에 뭐 있든 specificity (0,0,0,0) 기여. 디자인 시스템이 필요로 하는 정확히 그것: 사소하게 override 가능한 베이스 스타일.
Selector 리스트 가진 :not()
모던 :not() 이 selector 리스트 받음:
li:not(:first-child, :last-child) { /* 모든 가운데 항목 */ }
button:not([disabled], [aria-disabled="true"]):hover { /* hover 상태 */ }
옛 형식 (:not() 마다 selector 하나) 도 여전히 작동하지만 리스트 형식이 짧고 읽기 쉬워. Specificity 규칙: :is() 와 같음 — 가장 높은 argument 의 specificity 받음.
:has() — 웹이 빌어 온 부모 selector
20 년 동안 가장 많이 요청된 CSS 기능이 "부모 selector" — element 를 안의 내용 기반으로 스타일링하는 방법. 답은 항상 JavaScript: 자식 바뀔 때 부모에 class 토글. :has() 가 2023 년에 마침내 native 로 도착 (모든 모던 브라우저에 baseline 지원):
.card:has(img)— 이미지 포함하는 어떤.card.article:has(> h1)— 직접<h1>자식 가진 article.form:has(input:invalid)— 적어도 하나의 유효하지 않은 input 가진 폼.label:has(input[required])::after— required input 감싸는 label 에 빨간 별표 추가.li:has(+ li:hover)— hover 중인 거 바로 앞 항목.html:has(body.dark)— body 의 class 기반으로 html element 스타일링.
:has() 안의 관계는 어떤 combinator 든 (descendant 공백, child >, sibling +, ~). 진정한 관계 selector.
:has() 가 풀어낸 패턴
JavaScript 필요했던 일부 인터랙션이 이제 순수 CSS:
- 유효하지 않은 필드 가진 폼 → disabled 처럼 보이는 submit 버튼.
form:has(:invalid) button[type="submit"] { opacity: 0.5; } - 메인 콘텐츠 비어 있으면 사이드바 접힘.
.layout:has(main:empty) aside { display: none; } - 자식 링크 hover 되면 카드 하이라이트.
.card:has(a:hover) { background: var(--accent); } - Body class 기반 테마 반전.
html:has(body.dark) { color-scheme: dark; } - 첨부 가진 대화에 'has notes' 인디케이터 표시.
.conversation:has(.attachment) .indicator { display: inline; }
:has() 가 일급 도구. CSS-as-DOM-projection 시대 끝났어.:nth-child(of selector) — 또 다른 모던 파워
옛 :nth-child(2n) 이 type 무시하고 모든 sibling 중에 셈. :nth-of-type(2n) 이 같은 element type sibling 만 셈. 새 :nth-child(2n of .card) 가 selector 매치하는 sibling 만 셈 — ".card 매치하는 매 다른 element". 이전엔 스크립트 없이 불가능:
2026 년 Selector 퍼포먼스
복잡한 selector 의 퍼포먼스 비용이 거의 모든 사이트에 사라질 만큼 작아. 모던 브라우저 엔진 (Blink, Gecko, WebKit) 이 공격적 최적화 가짐: selector 캐싱, ancestor invalidation, has-state tracking. "퍼포먼스 위해 descendant selector 피해" 옛 조언은 outdated. 가장 명료한 selector 적어. 문제 측정한 경우에만 프로파일.
브라우저 지원, 깔끔하게
Baseline 2023 기능 (주요 브라우저에 widely available): :is(), :where(), :has(), selector 리스트 가진 :not(), :nth-child(of selector). 써. Caniuse.com 이 특정 옛 브라우저 요구 있으면 per-browser 버전 cutoff 알려 줘.
피파의 노트
:has() 위에 전적으로 지어진 패턴 둘: (1) 채팅 메시지 리스트가 li:has(.attachment) .badge 써서 'has attachment' 배지 추가 — JavaScript class 토글 없음, observer 없음. (2) council UI 가 brain 선택 안 됐을 때 picker 흐릿하게: .council-panel:has(.brain-select:invalid) .picker { opacity: 0.5; }. 옛 접근법 (상태 구독, class 토글) 이 이제 그냥 CSS selector. 코드 적게, 버그 적게, 렌더 빠르게.