두 규칙이 같은 요소에 적용될 때 어느 쪽이 이길까요? CSS 명시도(Specificity)가 그 답입니다. 명시도를 이해하지 못하면 !important를 남발하거나 “왜 스타일이 안 먹히지?”라고 DevTools와 씨름하게 됩니다. 이 가이드에서는 계산 방법, 흔한 함정, 그리고 계산기를 이용해 충돌을 빠르게 해결하는 방법을 설명합니다.

CSS 명시도 온라인 확인 →

명시도 계산 방법

명시도는 (a, b, c) 세 자리 숫자로 표시합니다:

무엇을 세는가
aID 선택자(#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 하나가 클래스 몇 개든 이깁니다.

비교 규칙: 왼쪽부터 순서대로

  1. a를 비교. 큰 쪽이 이김. 같으면 b
  2. b를 비교. 큰 쪽이 이김. 같으면 c
  3. 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()를 사용하는 속성의 명시도는 변수 정의 위치가 아닌 선택자로 결정됩니다.

명시도 충돌 디버깅 절차

스타일이 적용되지 않을 때 전형적인 과정:

  1. DevTools → 요소 검사 → Styles 패널 확인
  2. 취소선이 그어진 규칙은 덮어쓰여진 것 — 마우스를 올려 승자를 확인
  3. 충돌하는 두 선택자를 CSS 명시도 계산기에 붙여 넣어 점수 비교
  4. 지는 선택자의 명시도를 높이거나 구조를 재검토

수정 예시

/* 문제: 이 규칙이 적용되지 않음 */
.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 도구

손으로 계산할 필요 없이, 계산기로 바로 확인

ZeroTool CSS 명시도 계산기는 모든 선택자를 파싱해 정확한 (a, b, c) 점수를 반환하고, 각 기여 부분을 강조 표시하며, 여러 선택자를 나란히 비교할 수 있습니다. 회원가입 불필요, 브라우저에서 실행됩니다.

CSS 명시도 계산기 사용하기 →