01 — Cascade Algorithm
한 줄 답: 한 요소의 최종 스타일은 단일 비교가 아니라 8단 토너먼트의 결과다. Specificity는 그 토너먼트의 6번째 라운드일 뿐, 그 앞에 origin·layer·scope가 있고 뒤에 order가 있다.
Why — 왜 “단순 비교”로는 안 되는가
CSS 한 요소에 적용되는 선언은 흔히 수십, 수백 개다.
<button class="btn primary" id="cta">Buy</button>이 버튼에 영향을 줄 수 있는 곳:
| 출처 | 예 |
|---|---|
| 브라우저 기본 (UA) | button { font: -webkit-small-control } |
| 사용자 스타일시트 | button { font-size: 18px !important; } (시각장애 사용자) |
| 작성자 CSS | .btn { color: white } |
| 라이브러리 (Tailwind, MUI) | .btn-primary { background: blue } |
인라인 style | <button style="color: red"> |
@keyframes 애니메이션 | to { color: gold } |
| transition | transition: color 200ms 중간값 |
이 7개에서 어떤 값이 최종으로 적용되어야 하는가? — 이게 cascade의 문제다.
단순히 “specificity 높은 게 이긴다”로는 부족하다. 사용자의 접근성 설정이 작성자보다 우선해야 할 때가 있고, 애니메이션은 specificity와 무관하게 작동해야 한다. 그래서 8단계가 필요했다.
How — 8단 토너먼트
CSS Cascade Level 5/6 명세에 따른 정식 순서다. 위에서 결판나면 아래는 보지도 않는다.
1. Origin & Importance (가장 강한 분류)
2. Context (Encapsulation) (Shadow DOM, Slot 등)
3. Element-attached styles (transition / animation)
4. Cascade Layers (@layer)
5. Cascade Scope (proximity)
6. Specificity (a, b, c)
7. Order of Appearance (마지막에 선언된 게 이김)(실무 기준 7단계로 외워도 충분. 명세는 context를 한 단으로 분리해 8단으로 본다.)
1) Origin & Importance — 1라운드
| 순위 | 출처 | 비고 |
|---|---|---|
| 1 (최강) | Transition (실행 중) | 어떤 것도 못 이긴다 |
| 2 | UA !important | 브라우저가 강제하는 안전장치 |
| 3 | User !important | 사용자가 작성자를 이긴다 |
| 4 | Author !important | 작성자가 쓴 !important |
| 5 | Animation (실행 중) | @keyframes 중간 값 |
| 6 | Author (normal) | 평소 작성자 CSS |
| 7 | User (normal) | 사용자 스타일시트 |
| 8 (최약) | UA (normal) | 브라우저 기본 |
핵심 반전: !important가 붙으면 순서가 뒤집힌다. User > Author > UA. 접근성 우선 설계다.
/* 사용자 스타일시트 (브라우저 설정) */
* { font-size: 18px !important; } /* 작성자가 어떤 !important를 써도 못 막음 */2) Context — 2라운드
Shadow DOM과 Slot의 인캡슐레이션 우선순위. 대부분의 일반 페이지에선 의식할 일 없다.
- Slot으로 내려온 콘텐츠의 스타일 vs Shadow DOM 내부 스타일 → outer가 이긴다.
3) Element-attached styles — 3라운드
transition 중인 값은 현재 진행 중일 때만 별도 라운드에서 최강이 된다.
.btn { color: red; transition: color 1s; }
.btn:hover { color: blue; }hover 시작 후 0.5초 시점에는 color: purple(중간값)이 적용되는데, 이 중간값은 이후 모든 선언을 무시한다. transition이 끝나면 일반 cascade로 돌아온다.
4) Cascade Layers — 4라운드
@layer reset, framework, components, utilities;
@layer utilities {
.text-red { color: red; } /* 가장 늦게 선언된 레이어가 강함 */
}
@layer components {
.btn { color: blue; }
}같은 origin·!important 분류 안에서 나중에 선언된 레이어가 이긴다. specificity를 보기 전에 layer가 결정한다 — 즉 낮은 layer의 #id #id #id도 높은 layer의 .class에 진다.
레이어에 속하지 않은 규칙은 모든 layer보다 강하다(layer 없음 = “unlayered” = 가장 강한 layer 취급).
!important를 쓰면 layer 순서가 역전된다. 가장 먼저 선언된 layer의 !important가 이긴다. (이유: !important의 정신은 “근본을 보호” — 가장 먼저 정한 reset/base가 보호되도록.)
5) Cascade Scope — 5라운드
@scope (.card) to (.card-footer) {
h1 { color: red; } /* 카드 안의 h1만 */
}@scope로 묶인 규칙들 사이에서, 스코프 루트에 더 가까운(proximity가 짧은) 규칙이 이긴다. specificity 동률 시 적용된다.
6) Specificity — 6라운드
(a, b, c) 튜플 비교. 자세한 건 다음 문서.
| 카테고리 | 예 | 점수 |
|---|---|---|
| a — ID | #header | (1,0,0) |
| b — class/attr/pseudo-class | .btn, [type], :hover | (0,1,0) |
| c — type/pseudo-element | button, ::before | (0,0,1) |
| inline style | style="..." | (1,0,0,0) — a보다 위 |
비교는 사전식. (1,0,0) > (0,99,99). 단일 ID 하나가 클래스 99개를 이긴다.
7) Order of Appearance — 7라운드
위 6라운드까지 동률이면 나중에 선언된 규칙이 이긴다.
.btn { color: red; }
.btn { color: blue; } /* 이게 이김 */@import 순서, <link> 순서, <style> 순서가 모두 영향을 준다. 동일 파일 안에서는 위→아래.
What — 실제 사고 추적
예 1: 라이브러리 vs 자체 CSS
{/* 헤더에서 */}
<link rel="stylesheet" href="bootstrap.css"> {/* .btn { color: white } */}
<link rel="stylesheet" href="site.css"> {/* .btn { color: black } */}site.css의 .btn은 specificity 동률, order 더 늦음 → black 이김.
예 2: 한 줄에 !important 추가
/* bootstrap.css */
.btn { color: white !important; }
/* site.css */
.btn { color: black; }bootstrap의 !important가 normal을 이긴다 → white 이김. site.css가 더 늦게 로드돼도 무관.
예 3: @layer로 해결
@layer framework, site;
@layer framework {
.btn { color: white !important; }
}
@layer site {
.btn { color: black; } /* !important 없이도 이김 */
}site 레이어가 더 늦게 선언됨 → 레이어 라운드에서 site 승. !important가 framework에 있어도 같은 분류(normal vs important) 안에서 비교되므로… 어? framework가 important면 site는 normal이라 라운드 1에서 짐.
/* 올바른 해결 */
@layer framework, site;
@layer framework {
.btn { color: white !important; } /* important에서는 먼저 선언된 layer가 강함 */
}
@layer site {
.btn { color: black !important; } /* 같은 important + 나중 layer = 짐 */
}
/* → !important 분류에서 framework가 이긴다. white 적용 */교훈: !important + @layer는 직관과 반대다. 가급적 !important를 안 쓰는 구조를 만들자.
예 4: 인라인 style은 어떻게 분류되나
<div style="color: red"></div>
<style>
div { color: blue !important; }
</style>- 인라인 style: author normal, specificity (1,0,0,0) — class보다 강함
<style>의!important: author important
라운드 1에서 important 분류가 더 강함 → blue 이김.
예 5: animation의 위치
.btn { color: red; }
@keyframes shake {
to { color: green; }
}
.btn { animation: shake 1s infinite; }animation 중에는 animation 분류가 author normal보다 위(라운드 1) → green이 적용되며 흔들림.
하지만:
.btn { color: red !important; }이건 author important → animation을 이긴다 → red 고정.
What-if — 잘못 알면
사고 1: “ID는 최강”이라고 믿기
#sidebar a { color: red; }
.nav a.active { color: blue !important; }specificity는 (1,0,1) vs (0,2,1)로 ID가 이기지만, !important 때문에 라운드 1에서 결판 → blue. specificity는 6라운드에서나 보인다.
사고 2: !important를 만능 해머로 쓰기
라이브러리가 !important를 안 쓰는데도 .btn { color: red !important }를 도배하면:
- 향후 디자인 변경 때 다른
!important로 덮어야 한다 → 폭주. - 사용자 접근성 설정을 무력화한다 (사용자 normal은 작성자 important에 짐).
해결: @layer로 우선순위를 구조적으로 표현.
사고 3: animation이 transition을 이긴다고 착각
순위는 transition (3라운드) > animation (1라운드의 5순위). transition은 element-attached로 별도 처리되어 더 강하다.
.btn {
background: red;
animation: pulse 1s infinite; /* red ↔ green */
transition: background 500ms;
}
.btn:hover { background: blue; }hover하면 transition이 발동 → animation 중간값을 무시하고 blue로 매끄럽게 전이.
Insight — 왜 8단인가 (역사)
CSS1 (1996)은 단순했다. cascade는 origin(작성자/사용자/UA) + specificity + order의 3단계였다.
| 연도 | 추가된 라운드 | 이유 |
|---|---|---|
| 1996 | origin, specificity, order | CSS1 |
| 1998 | !important 도입 | 사용자 우선권 보장 |
| 2009 | transition/animation | 모션 추가 |
| 2022 | @layer (CSS Cascade 5) | 작성자 내부 우선순위를 코드로 |
| 2023+ | @scope proximity (CSS Cascade 6) | 컴포넌트 격리 |
중요한 흐름: 라운드는 위에 추가되었지 아래에 추가되지 않았다. specificity는 26년간 같은 자리에 있다. 추가된 것은 그보다 더 거시적인 분류 기준들이다.
이는 CSS가 “규칙 하나가 이기는 모델”에서 “규칙 그룹들이 협상하는 모델”로 진화했음을 보여준다. 디자인 시스템과 라이브러리가 평화롭게 공존할 도구가 필요했던 것이다.
요약 + Mermaid
- 8단 토너먼트: origin/!important → context → element-attached → layer → scope → specificity → order.
!important는 라운드 1에서 origin 순서를 뒤집는다 (user > author > UA).@layer는 라운드 4 — specificity 전에 결판난다.@scope는 라운드 5 — proximity로 동률 해결.- Specificity는 6라운드. 위에 4개의 더 강한 게이트가 있다.
다음: 02-specificity에서 (a,b,c) 튜플을 정확히 계산한다.