두 규칙이 같은 요소에 적용될 때 어느 쪽이 이길까요? CSS 명시도(Specificity)가 그 답입니다. 명시도를 이해하지 못하면 !important를 남발하거나 “왜 스타일이 안 먹히지?”라고 DevTools와 씨름하게 됩니다. 이 가이드에서는 계산 방법, 흔한 함정, 그리고 계산기를 이용해 충돌을 빠르게 해결하는 방법을 설명합니다.
명시도 계산 방법
명시도는 (a, b, c) 세 자리 숫자로 표시합니다:
| 열 | 무엇을 세는가 |
|---|---|
a | ID 선택자(#id) |
b | 클래스(.class), 속성([type]), 의사 클래스(:hover) |
c | 요소(div, p), 의사 요소(::before) |
인라인 스타일(style="")은 세 열 위에 위치하며 가장 높은 우선순위를 가집니다.
예시
/* 명시도: (0, 0, 1) — 요소 하나 */
p { color: red; }
/* 명시도: (0, 1, 0) — 클래스 하나 */
.intro { color: blue; }
/* 명시도: (0, 1, 1) — 클래스 하나 + 요소 하나 */
p.intro { color: green; }
/* 명시도: (1, 0, 0) — ID 하나 */
#header { color: purple; }
/* 명시도: (1, 1, 0) — ID 하나 + 클래스 하나 */
#header .nav { color: orange; }
두 규칙이 충돌할 때 더 큰 숫자가 이깁니다. (1, 0, 0)은 (0, 99, 99)보다 높습니다 — ID 하나가 클래스 몇 개든 이깁니다.
비교 규칙: 왼쪽부터 순서대로
a를 비교. 큰 쪽이 이김. 같으면b로b를 비교. 큰 쪽이 이김. 같으면c로c를 비교. 큰 쪽이 이김. 그래도 같으면 소스 순서가 결정 — 나중에 작성된 규칙이 이김
/* (0,2,0) vs (0,1,2) */
/* a: 같음; b: 2 > 1 — 첫 번째 규칙이 이김 */
.nav .link { color: red; } /* (0,2,0) — 승 */
.nav li span { color: blue; } /* (0,1,2) */
명시도 계층
낮은 것부터 높은 것 순서:
요소/의사 요소 → 클래스/속성/의사 클래스 → ID → 인라인 스타일 → !important
(0,0,1) (0,1,0) (1,0,0) 최상위
!important는 명시도 값이 아니라 선언을 일반 캐스케이드 밖으로 끌어올립니다. !important를 덮어쓰려면 동등하거나 더 높은 명시도의 !important가 필요합니다.
자주 실수하는 함정
전체 선택자 *의 명시도는 0
*는 아무 숫자도 더하지 않습니다. *.active는 .active와 같은 (0,1,0)입니다.
:not() 자체는 0, 인수는 유효
:not(p)은 0을 추가하지만 괄호 안 p는 (0,0,1)을 추가합니다. 따라서 div:not(.hidden)은 (0,1,1)입니다.
:is(), :where(), :has()
:is()— 인수 목록 중 가장 높은 명시도를 취함:where()— 항상 0 (리셋 스타일에 유용):has()—:is()와 동일
/* (0,1,0) — :is()가 .active의 명시도를 취함 */
:is(.active, p) { color: red; }
/* (0,0,0) — :where()는 항상 0 */
:where(.active, p) { color: red; }
CSS 변수는 명시도에 영향 없음
var()를 사용하는 속성의 명시도는 변수 정의 위치가 아닌 선택자로 결정됩니다.
명시도 충돌 디버깅 절차
스타일이 적용되지 않을 때 전형적인 과정:
- DevTools → 요소 검사 → Styles 패널 확인
- 취소선이 그어진 규칙은 덮어쓰여진 것 — 마우스를 올려 승자를 확인
- 충돌하는 두 선택자를 CSS 명시도 계산기에 붙여 넣어 점수 비교
- 지는 선택자의 명시도를 높이거나 구조를 재검토
수정 예시
/* 문제: 이 규칙이 적용되지 않음 */
.card p { font-size: 14px; } /* (0,1,1) */
/* 이 규칙이 이기고 있음 */
#content p { font-size: 16px; } /* (1,0,1) */
/* 방법 1: 명시도 높이기 */
#content .card p { font-size: 14px; } /* (1,1,1) */
/* 방법 2 (권장): ID를 클래스로 변경해 평탄하게 유지 */
.content .card p { font-size: 14px; } /* (0,2,1) */
장기적으로는 스타일시트에서 ID 선택자를 사용하지 않는 것이 가장 좋은 해결책입니다.
명시도 실천 전략
명시도를 평탄하게 유지하세요. 선택자를 (0,1,0) ~ (0,2,1) 수준으로 유지하면 프로젝트 규모가 커질수록 이점이 됩니다.
CSS에서는 ID를 피하세요. ID는 JavaScript 훅으로만 사용하고 CSS에서는 클래스로 대체합니다. #header nav → .site-header .nav.
@layer로 캐스케이드를 관리하세요. CSS @layer를 사용하면 명시도와 독립적으로 캐스케이드 순서를 제어할 수 있습니다.
@layer base, components, utilities;
@layer base {
#header { color: black; } /* (1,0,0) */
}
@layer utilities {
.text-white { color: white; } /* (0,1,0) — 그래도 이김 */
}
!important는 최후의 수단으로. 먼저 선택자 재구성을 시도하세요.
관련 CSS 도구
- CSS 단위 변환기 — px, rem, em, vw 등 변환
- CSS 변수 생성기 — 디자인 토큰 관리
- CSS Grid 생성기 — 시각적으로 그리드 레이아웃 구성
손으로 계산할 필요 없이, 계산기로 바로 확인
ZeroTool CSS 명시도 계산기는 모든 선택자를 파싱해 정확한 (a, b, c) 점수를 반환하고, 각 기여 부분을 강조 표시하며, 여러 선택자를 나란히 비교할 수 있습니다. 회원가입 불필요, 브라우저에서 실행됩니다.