다음 두 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 의 의사결정 순서
한 요소에 여러 룰이 매치되면 어떤 게 이기는지의 순서:
- Origin & Importance — User agent / User / Author.
!important우선 - Layer order —
@layer의 선언 순서 (모던 추가) - Specificity — (a-b-c) tuple 비교
- 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:
- User Agent (browser default) important
- User stylesheet important
- Author CSS important
- Animation
- Author CSS (normal)
- User stylesheet (normal)
- 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. "왜 이 룰 안 먹히지?" 체크리스트
- typo? class name 정확?
- 특정도 낮아 다른 룰에 짐? → DevTools 의 strike-through
- cascade 순서 — 나중 선언이 이기는데, 더 먼저 선언됐나?
- inline style?
style="..."우선 - !important 누가 박았나?
- @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 가 권장.
참고 자료
- MDN — CSS Specificity — MDN
- CSS Cascade Module Level 5 — W3C
- CSS @layer (Bramus Van Damme) — bram.us
- Specificity calculator — specificity.keegan.st
요약
- 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) 시각 확인.