본문으로 건너뛰기
개발 머꼬
개발 노트CSS
hohyeon.dev15

:is()로 선택자를 묶었더니 개별 규칙의 예외가 먹지 않은 이유

  • #Common Pitfall
  • #CSS
  • #Engineering Note

문제 발생

여러 컨테이너에서 같은 링크 스타일을 쓰려고 :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) 이라는 성질도 공유합니다. 목록 안에 브라우저가 모르는 선택자가 있어도 그것만 무시되고 나머지는 살아남습니다. 일반 선택자 목록에서는 하나가 잘못되면 규칙 전체가 버려집니다.

해결 방안

  1. 덮어쓸 여지를 남기려면 :where()를 씁니다. 리셋, 기본 스타일, 라이브러리의 기본값처럼 "누구든 쉽게 이기게" 하고 싶은 규칙에 맞습니다.
:where(section.content, aside.content, footer.content) a {
  color: red;
}

footer a {
  color: blue;   /* 이제 적용됨 */
}
  1. 의도한 무게가 있으면 :is()를 씁니다. 명시도를 유지해야 하는 규칙이라면 :is()가 맞습니다. 무엇을 쓰는지가 곧 "이 규칙이 얼마나 세야 하는가"의 선언이 됩니다.

  2. 명시도를 0으로 만드는 용도로도 씁니다. 태그 선택자 하나만 감싸도 명시도가 사라집니다.

:where(h1, h2, h3) { margin-block: 0; }   /* 무엇으로든 덮어쓸 수 있음 */
  1. 신구 문법을 함께 쓸 때 관용적 목록이 유용합니다. 지원하지 않는 브라우저에서 규칙 전체가 날아가지 않습니다.
:where(:valid, :user-valid) { border-color: green; }
  1. 의사 요소는 넣을 수 없습니다. :is(::before, ::after)는 동작하지 않습니다.

  2. !important로 이기려 하기 전에 이걸 먼저 봅니다. 명시도 싸움의 상당수는 "이 규칙이 왜 이렇게 센가"를 못 본 것이고, :where()는 그 무게를 애초에 만들지 않는 쪽의 도구입니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.