CSS Specificity
같은 요소에 여러 CSS 규칙이 적용될 때, 어떤 규칙이 이기는지를 결정하는 무게 체계를 Specificity(명시도)라고 한다.
CSS에서 스타일 충돌이 발생하면, 브라우저는 각 셀렉터의 specificity를 계산해서 더 높은 쪽을 적용한다. Specificity는 CSS Cascade 알고리즘의 한 단계다.
계산법
Specificity는 네 자리 숫자로 표현한다. 각 자리는 셀렉터 구성 요소의 개수를 센다.
| 자리 | 대상 | 예시 |
|---|---|---|
| 1번째 (최고) | inline style (style="") | style="color: red" |
| 2번째 | id 셀렉터 | #header |
| 3번째 | class, attribute, pseudo-class | .active, [type="text"], :hover |
| 4번째 (최저) | element, pseudo-element | div, p, ::before |
수치로 표현하면 inline(1,0,0,0) > id(0,1,0,0) > class(0,0,1,0) > element(0,0,0,1)이다.
복합 셀렉터 계산
셀렉터가 복합적이면 각 구성 요소를 더한다.
/* (0, 0, 1, 1) — 클래스 1개 + 요소 1개 */
.markdown-content img { ... }
/* (0, 0, 1, 0) — 클래스 1개 */
.w-3\.5 { ... }
/* (0, 1, 1, 0) — id 1개 + 클래스 1개 */
#main .active { ... }
/* (0, 0, 2, 1) — 클래스 2개 + 요소 1개 */
.sidebar .nav a { ... }
.markdown-content img는 (0,0,1,1)이고 .w-3\.5는 (0,0,1,0)이다. 3번째 자리가 같으면 4번째 자리를 비교하므로 .markdown-content img가 이긴다. Tailwind 유틸리티 클래스가 자손 셀렉터에 지는 대표적인 경우다.
:not() 셀렉터의 specificity
:not() 자체는 specificity에 기여하지 않는다. 대신 괄호 안에 있는 셀렉터 중 가장 높은 specificity가 반영된다.
/* :not() 자체는 0, 안의 .not-prose가 (0,0,1,0) */
/* 전체: (0, 0, 2, 1) — .markdown-content(1) + :not(.not-prose *)(1) + img(1) */
.markdown-content img:not(.not-prose *, .not-prose) { ... }
CSS Selectors Level 4에서는 :not() 안에 복합 셀렉터를 넣을 수 있다. .not-prose *는 ".not-prose의 자손인 모든 요소"를 의미한다.
:not()으로 영역 제외하기
커스텀 CSS가 특정 영역의 컴포넌트까지 관통하는 문제를 :not()으로 해결할 수 있다.
/* Before: .markdown-content 안의 모든 img에 적용 */
.markdown-content img {
max-width: 100%;
border-radius: 0.5rem;
}
/* After: .not-prose 영역 안의 img에는 적용하지 않음 */
.markdown-content img:not(.not-prose *, .not-prose) {
max-width: 100%;
border-radius: 0.5rem;
}
이 패턴은 마크다운 렌더링 영역 안에 독립적인 컴포넌트(카드, 위젯 등)를 삽입할 때 유용하다. 컴포넌트를 <div class="not-prose">로 감싸기만 하면 마크다운 CSS에서 자동으로 제외된다.
Tailwind Typography 플러그인도 같은 원리로 not-prose 클래스를 지원한다. 다만 커스텀 CSS에는 자동 적용되지 않으므로 직접 :not() 조건을 추가해야 한다.
Specificity와 Cascade
Specificity는 CSS가 스타일 충돌을 해결하는 전체 알고리즘(Cascade)의 한 단계일 뿐이다. Cascade의 판정 순서는 Origin → Importance(!important) → Layer(@layer) → Specificity → Source Order다. Specificity가 같으면 나중에 작성된 규칙이 이긴다.
정리
- Specificity는 inline > id > class > element 순서로 비교한다
- 복합 셀렉터는 각 구성 요소를 더해서 계산한다
:not()자체는 specificity 0이고, 안의 셀렉터만 반영된다- Tailwind 유틸리티(단일 클래스)가 자손 셀렉터(클래스+요소)에 지는 경우, specificity를 높이는 것보다
:not()으로 적용 범위를 제한하는 게 근본적이다