:is()로 선택자를 묶었더니 개별 규칙의 예외가 먹지 않은 이유
문제 발생
여러 컨테이너에서 같은 링크 스타일을 쓰려고 :is()로 묶었더니, 정작 개별 규칙으로 예외를 주는 곳에서 그 예외가 먹지 않았습니다.
:is(section.content, aside.content, footer.content) a {
color: red;
}
footer a {
color: blue; /* 적용 안 됨 */
}선택자를 길게 늘여 쓰던 때는 없던 문제였습니다.
원인 분석
:is()와 :where()는 하는 일이 같습니다 — 선택자 목록을 받아 그중 하나에 걸리는 요소를 고릅니다. 다른 것은 명시도(specificity) 하나뿐인데, 그게 결정적입니다.
MDN이 그대로 적습니다 — :where()는 항상 명시도가 0이고, :is()는 인자 중 가장 명시도가 높은 선택자의 명시도를 가져옵니다.
위 예제에서 :is(...) 안에 .content라는 클래스가 있으므로 그 규칙 전체가 "클래스 1개 + 타입 1개"의 무게를 갖습니다. footer a는 타입 2개뿐이라 집니다. 선택자를 풀어 썼을 때는 section.content a / aside.content a / footer.content a가 각각 계산되므로 체감이 달랐던 것입니다.
두 함수는 관용적 선택자 목록(forgiving selector list) 이라는 성질도 공유합니다. 목록 안에 브라우저가 모르는 선택자가 있어도 그것만 무시되고 나머지는 살아남습니다. 일반 선택자 목록에서는 하나가 잘못되면 규칙 전체가 버려집니다.
해결 방안
- 덮어쓸 여지를 남기려면
:where()를 씁니다. 리셋, 기본 스타일, 라이브러리의 기본값처럼 "누구든 쉽게 이기게" 하고 싶은 규칙에 맞습니다.
:where(section.content, aside.content, footer.content) a {
color: red;
}
footer a {
color: blue; /* 이제 적용됨 */
}-
의도한 무게가 있으면
:is()를 씁니다. 명시도를 유지해야 하는 규칙이라면:is()가 맞습니다. 무엇을 쓰는지가 곧 "이 규칙이 얼마나 세야 하는가"의 선언이 됩니다. -
명시도를 0으로 만드는 용도로도 씁니다. 태그 선택자 하나만 감싸도 명시도가 사라집니다.
:where(h1, h2, h3) { margin-block: 0; } /* 무엇으로든 덮어쓸 수 있음 */- 신구 문법을 함께 쓸 때 관용적 목록이 유용합니다. 지원하지 않는 브라우저에서 규칙 전체가 날아가지 않습니다.
:where(:valid, :user-valid) { border-color: green; }-
의사 요소는 넣을 수 없습니다.
:is(::before, ::after)는 동작하지 않습니다. -
!important로 이기려 하기 전에 이걸 먼저 봅니다. 명시도 싸움의 상당수는 "이 규칙이 왜 이렇게 센가"를 못 본 것이고,:where()는 그 무게를 애초에 만들지 않는 쪽의 도구입니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.