본문으로 건너뛰기
yutils

CSS 우선순위는 어떻게 결정될까? (specificity · cascade)

어떤 CSS 룰이 이기는지 결정하는 메카니즘 — selector matching, (a-b-c) specificity 계산, cascade 순서, :where() 의 zero specificity, !important 탈출구, 그리고 @layer 의 등장.

약 8분 읽기

다음 두 CSS — 어느 색이 적용되나?

#main p { color: blue; }
.intro    { color: red; }

<p id="main" class="intro">Hello</p>
                            ↓
                          무슨 색?

답: blue. 왜? #main p 가 더 specific 하다. 하지만 그게 무슨 의미인가? 이 가이드는 CSS 의 selector matching, (a-b-c) specificity 계산, cascade 순서, :where() 의 zero specificity, !important 의 탈출구, 그리고 @layer 의 새 질서를 정리한다.

Cascade — CSS 의 의사결정 순서

한 요소에 여러 룰이 매치되면 어떤 게 이기는지의 순서:

  1. Origin & Importance — User agent / User / Author. !important 우선
  2. Layer order@layer 의 선언 순서 (모던 추가)
  3. Specificity — (a-b-c) tuple 비교
  4. Source order — 같은 specificity 면 나중에 선언된 것 (cascade)

Specificity — (a, b, c) tuple

Selector 마다 점수 계산:

a = ID selector 수            (#main = 1)
b = class / attribute / pseudo-class 수  (.intro, [type], :hover = 1)
c = type / pseudo-element 수  (p, ::before = 1)

비교:
(1, 0, 0) > (0, 99, 99)    ← ID 1 개가 class 99 개 이김
(0, 1, 0) > (0, 0, 99)     ← class 1 개가 type 99 개 이김

흔한 selector 의 specificity:

Selector(a, b, c)
*(0, 0, 0)
p(0, 0, 1)
p::before(0, 0, 2)
.intro(0, 1, 0)
p.intro(0, 1, 1)
[type="text"](0, 1, 0)
:hover(0, 1, 0)
#main(1, 0, 0)
#main p(1, 0, 1)
#main #header(2, 0, 0)

inline style="..." 은 별도 (1000) 수준이라 대부분의 selector 보다 강함. !important 가 최강.

:where() — zero specificity 의 마법

2021 표준. CSS framework 사용자에게 강력:

/* 일반 :is() — 내부 selector 의 가장 높은 specificity */
:is(.btn, #header) p     ← (1, 0, 1)  ← #header 가 결정

/* :where() — 항상 (0, 0, 0) */
:where(.btn, #header) p  ← (0, 0, 1)  ← 내부 selector 의 specificity 무시

용도:

  • CSS reset / framework — 사용자 override 쉽게
  • Tailwind 의 일부 util — specificity 충돌 회피
  • 대형 component library — base styling 의 specificity 낮춤
/* CSS reset */
:where(h1, h2, h3, h4, h5, h6) {
  margin: 0;
  font-weight: 600;
}

/* 사용자 override — 단순 selector 로 가능 */
h1 { margin: 1rem; }     /* (0, 0, 1) > (0, 0, 0) → 이김 */

!important — 최후의 수단

p { color: blue !important; }
#main p { color: red; }

→ 블루 (important 우선)

# 더 강한 important 끼리?
p { color: blue !important; }     (0, 0, 1)
#main p { color: red !important; } (1, 0, 1) ← 이김

important 의 cascade:

  1. User Agent (browser default) important
  2. User stylesheet important
  3. Author CSS important
  4. Animation
  5. Author CSS (normal)
  6. User stylesheet (normal)
  7. User Agent (normal)

accessibility 위해 user stylesheet (의 important) 가 author 의 important 보다 우선 — 일부 브라우저. 시각 장애인 사용자가 폰트 크기 강제 가능.

@layer — 모던 cascade 제어 (2022)

Specificity 와 source order 만으로는 한계 — 큰 프로젝트의 css 관리 어려움. @layer 가 명시적 layer order 도입:

@layer reset, framework, components, utilities;

@layer reset {
  * { margin: 0; }
}

@layer framework {
  .btn { background: blue; }   /* (0, 1, 0) */
}

@layer components {
  .btn-primary { background: green; }  /* (0, 1, 0) */
}

@layer utilities {
  .bg-red { background: red !important; }
}

/* 결과 — layer 순서가 specificity 보다 우선:
   utilities > components > framework > reset
   layer 안에서만 specificity 비교 */

효과:

  • framework (Bootstrap, Tailwind) 를 reset layer 다음에 박음
  • component CSS 가 framework 보다 위에 → !important 불필요
  • layer 안 specificity 계산은 정상 — 같은 layer 내에서만 적용

Tailwind v4 default — 3 layer (base / components / utilities). Bootstrap 5.3+ 도 도입.

Cascade 의 marriage — Inheritance

Specificity 와 별개. 일부 property 가 부모로부터 inherit:

Inheritable:
  color, font, line-height, letter-spacing, visibility, cursor, ...

Non-inheritable:
  background, border, margin, padding, position, display, ...

매치되는 룰 없을 때 부모의 inherited value 사용. 매치되는 룰이 있으면 그 룰이 이김 (inherit 이 아닌).

명시적 inherit:

* {
  color: inherit;  /* 모든 element 가 부모로부터 color 받음 */
}

button {
  background: inherit;  /* button 의 default browser bg 무시 */
}

Specificity 디버깅 — 실전

1. browser DevTools

Element inspector 의 "Computed" 탭 — 어떤 룰이 이기는지 정확히 표시. 다른 룰은 strike-through.

2. specificity calculator

Selector 의 (a, b, c) 계산 — Keegan Street 의 specificity.keegan.st 같은 도구.

3. "왜 이 룰 안 먹히지?" 체크리스트

  1. typo? class name 정확?
  2. 특정도 낮아 다른 룰에 짐? → DevTools 의 strike-through
  3. cascade 순서 — 나중 선언이 이기는데, 더 먼저 선언됐나?
  4. inline style? style="..." 우선
  5. !important 누가 박았나?
  6. @layer 안인가? layer 순서 확인

흔한 함정

1. ID 남발

/* Bad — ID 가 specificity 너무 높음 */
#sidebar { width: 200px; }
.sidebar-collapsed { width: 50px; }   /* 안 먹힘 — (0,1,0) < (1,0,0) */

/* Good — ID 회피, class 만 */
.sidebar { width: 200px; }
.sidebar.collapsed { width: 50px; }   /* (0,2,0) > (0,1,0) → 이김 */

2. nested selector 너무 깊이

/* Bad */
.header .nav .menu .item a { color: blue; }   /* (0, 4, 1) */

/* 사용자 override 하려면 (0, 5, 0) 이상 필요 → 어려움 */

/* Good */
.menu-item { color: blue; }                   /* (0, 1, 0) */

3. !important 폭주

해결책으로 !important 박음 → 다른 곳에서 또 박아야 override → important 무한 추가. 결국 specificity 무력화. 첫 important 박기 전 cascade 점검.

4. Tailwind + custom CSS 충돌

<div class="bg-blue-500" style="background: red">
                ↓
                inline style 이김 (Tailwind class < inline)

<div class="bg-blue-500">
<style>
  .bg-blue-500 { background: red !important; }
</style>
                ↓
                동일 specificity + important → source order 결정

Tailwind v4 의 @layer utilities 가 이걸 정리.

5. CSS-in-JS 의 hash class

styled-components / emotion 의 hash class (.css-abc123) 는 specificity (0, 1, 0). user override 가 (0, 1, 0) 보다 강해야 이김. :where() 로 wrap 하면 (0, 0, 0) — override 쉬워짐.

Selector matching 의 흐름

Browser 가 어떻게 selector 와 element 를 매치하나? 효율 위해 우측 → 좌측:

Selector: .container .item .button

DOM 의 모든 .button element 부터 시작
  → 각 .button 의 ancestor 중 .item 있는가?
    → 있다면 그 ancestor 의 ancestor 에 .container 있는가?
      → 매치
    → 없으면 skip

그래서 깊은 nested selector 가 비효율. 매 element 마다 ancestor chain 탐색. 모던 브라우저는 indexing 최적화로 빠르지만 여전히 flat selector 가 권장.

참고 자료

요약

  • CSS 의 결정 순서 — Origin/Importance → Layer → Specificity → Source order.
  • Specificity = (a, b, c) tuple. a=ID, b=class/attr/pseudo-class, c=type/pseudo-element.
  • inline style 은 specificity 1000 급. !important 가 최강.
  • :where() = zero specificity. framework / reset 에 유용.
  • @layer (2022) = 명시적 layer 순서. specificity 보다 우선. 큰 프로젝트의 cascade 제어 표준.
  • Inheritance = 일부 property (color, font 등) 가 부모로부터. Specificity 와 별개.
  • 디버깅 — DevTools 의 Computed tab + specificity calculator.
  • 실험 — CSS 포매터 로 CSS 정리 후 selector 의 (a,b,c) 시각 확인.
가이드 목록으로