CSS에서 하나의 HTML 요소에 여러 개의 스타일 규칙이 동시에 적용될 수 있습니다. 예를 들어, 외부 CSS 파일, 내부 스타일 태그, 인라인 스타일이 모두 같은 요소의 색상을 다르게 지정할 수 있습니다. 이러한 충돌 상황에서 브라우저는 어떤 스타일을 최종적으로 적용할지 결정해야 하는데, 이때 사용되는 알고리즘이 바로 캐스케이드(Cascade)입니다. 캐스케이드는 CSS의 C가 의미하는 ‘Cascading’의 핵심이며, CSS가 어떻게 동작하는지를 근본적으로 이해하는 데 필수적인 개념입니다.
핵심 특징
- 여러 스타일 규칙이 충돌할 때 최종 적용 스타일을 결정하는 알고리즘
- 출처(Origin), 명시도(Specificity), 소스 순서(Source Order) 등 여러 요소를 고려하여 우선순위 판단
- 모든 CSS 속성에 일관되게 적용되는 체계적인 규칙
- 개발자가 작성한 스타일, 사용자 설정, 브라우저 기본 스타일이 모두 캐스케이드 대상
- CSS 전체 동작을 관통하는 핵심 메커니즘으로 상속(Inheritance)과 함께 작동
실무에서의 영향
캐스케이드를 이해하지 못하면 CSS 디버깅이 매우 어려워집니다. “왜 내가 작성한 스타일이 적용되지 않는가?” 같은 문제는 대부분 캐스케이드 알고리즘을 제대로 이해하지 못해서 발생합니다. 특히 대규모 프로젝트에서 여러 개발자가 작성한 스타일시트가 합쳐질 때, 또는 서드파티 CSS 라이브러리를 사용할 때 예상치 못한 스타일 충돌이 발생할 수 있습니다.
캐스케이드 원리를 정확히 알면 불필요한 !important 남용을 피하고, 예측 가능한 스타일 코드를 작성할 수 있습니다. 또한 CSS 설계 시 적절한 명시도 수준을 선택하여 유지보수가 쉬운 스타일시트를 구성할 수 있습니다. 캐스케이드는 단순히 이론적 개념이 아니라, 매일 작성하는 CSS 코드의 동작을 좌우하는 실질적인 메커니즘입니다. 프론트엔드 개발자라면 반드시 숙지해야 할 CSS의 가장 중요한 기초 원리입니다.
핵심 개념
캐스케이드 알고리즘의 본질
입문
CSS는 여러 규칙이 충돌할 때 어떤 스타일을 적용할지 자동으로 결정하는 알고리즘을 가지고 있어요. 이 알고리즘이 바로 캐스케이드예요!
🎯 캐스케이드가 뭔가요? 캐스케이드는 ‘폭포처럼 흐르는’이라는 뜻이에요. 스타일이 위에서 아래로 흘러내리면서, 충돌하는 규칙들이 있으면 자동으로 하나를 선택하는 거죠. 마치 물이 높은 곳에서 낮은 곳으로 자연스럽게 흐르는 것처럼요.
🚦 왜 필요한가요? 같은 버튼에 대해 여러 사람이 각각 다른 색을 지정했다고 생각해보세요. 빨간색, 파란색, 초록색… 브라우저는 어떤 색을 선택해야 할까요? 캐스케이드가 없다면 브라우저는 혼란에 빠질 거예요. 캐스케이드는 이런 충돌을 공정하게 해결하는 심판 같은 역할을 해요.
📚 어떻게 판단하나요? 캐스케이드는 세 가지를 차례대로 확인해요. 첫 번째로 “누가 작성했는지”를 봐요. 두 번째로 “얼마나 구체적으로 지정했는지”를 확인해요. 세 번째로 “어떤 게 나중에 작성되었는지”를 봐요. 이 순서대로 하나씩 체크하면서 최종 승자를 결정하는 거예요.
💡 실생활 비유 학교에서 체육대회 규칙을 정한다고 생각해보세요. 교육부가 만든 기본 규칙, 학교 선생님이 추가한 규칙, 학생회가 제안한 규칙이 있어요. 만약 규칙이 겹친다면? 보통 더 구체적이고 가까운 규칙(학생회)을 따르죠. 캐스케이드도 이런 식으로 작동해요!
중급
캐스케이드(Cascade)는 하나의 요소에 여러 CSS 규칙이 적용될 때 최종적으로 어떤 스타일 값을 사용할지 결정하는 알고리즘입니다.
캐스케이드의 핵심 동작 CSS는 “Cascading Style Sheets”의 약자이며, 여기서 Cascading이 바로 이 알고리즘을 의미합니다. 캐스케이드는 다음 세 가지 요소를 순차적으로 비교하여 승자를 결정합니다:
- Origin and Importance (출처와 중요도)
- Specificity (명시도)
- Order of Appearance (소스 순서)
이 순서는 절대적입니다. 첫 번째 단계에서 승자가 결정되면 두 번째, 세 번째 단계는 실행되지 않습니다.
/* 외부 CSS 파일 */
p {
color: blue;
}
/* 내부 스타일 태그 */
p {
color: red;
}
<p>이 텍스트는 무슨 색일까요?</p>
<!-- 결과: 빨간색 (같은 명시도일 때 나중에 선언된 규칙이 승리) -->
충돌 해결 프로세스
브라우저는 각 CSS 속성마다 개별적으로 캐스케이드를 실행합니다. 예를 들어 color 속성과 font-size 속성은 서로 독립적으로 캐스케이드 알고리즘을 거쳐 최종 값이 결정됩니다.
.text {
color: blue;
font-size: 16px;
}
#title {
color: red;
}
<p class="text" id="title">안녕하세요</p>
<!-- 결과: color는 red (ID 선택자가 더 명시적)
font-size는 16px (충돌 없음) -->
심화
캐스케이드는 CSS Working Group이 정의한 CSS Cascading and Inheritance Level 5 명세(W3C)에 따라 구현되며, 브라우저 렌더링 엔진의 스타일 해결(Style Resolution) 단계에서 실행되는 핵심 알고리즘입니다.
W3C 명세 기반 캐스케이드 계층 구조 CSS Cascading Level 5 명세에 따르면, 캐스케이드는 선언(Declaration)을 다음 순서로 정렬하여 최종 값을 결정합니다:
- Origin and Importance: Transition declarations → Important user agent → Important user → Important author → Normal author → Normal user → Normal user agent
- Context: 캡슐화 컨텍스트(Shadow DOM 등) 고려
- Specificity: ID-Class-Type 가중치 계산
- Order of Appearance: 소스 순서 (마지막 선언 우선)
이 과정은 각 CSS 속성마다 독립적으로 실행되며, 계산된 값(Computed Value)이 DOM 트리에 적용됩니다.
브라우저 엔진 구현 세부사항 Chromium(Blink 엔진)에서 캐스케이드는 StyleResolver 클래스의 MatchedPropertiesCache를 통해 최적화됩니다:
Cascade Resolution Optimization: 동일한 요소에 대한 캐스케이드 결과를 캐싱하여 재계산 비용을 절감합니다. 캐시 키는 (MatchResult, ParentStyle, PseudoId) 튜플로 구성되며, 높은 캐시 적중률을 보입니다.
Shadow DOM Cascade Isolation: Shadow DOM 경계는 별도의 캐스케이드 컨텍스트를 생성합니다. ::part() 및 CSS Custom Properties는 Shadow DOM 경계를 넘을 수 있지만, 일반 선택자는 격리됩니다.
성능 고려사항 캐스케이드 계산 비용은 O(n × m)입니다. 여기서 n은 적용 가능한 규칙 수, m은 요소 수입니다. 실제 환경에서는 다음 최적화가 적용됩니다:
- Rule Indexing: 선택자 유형별로 규칙을 인덱싱하여 불필요한 매칭 시도를 제거 (Bloom Filter 사용)
- Selector Filtering: 불가능한 선택자는 조기 제거 (예: ID가 다른 경우)
- Incremental Style Resolution: DOM 변경 시 영향받는 요소만 재계산 (Dirty Bit 플래그)
대규모 웹 애플리케이션에서 이러한 캐스케이드 최적화는 초기 렌더링 성능을 크게 향상시킵니다.
캐스케이드의 세 가지 판단 기준
입문
캐스케이드가 승자를 정할 때 세 가지를 순서대로 확인해요. 마치 게임에서 우선순위를 정하는 것처럼요!
🥇 첫 번째: 누가 작성했는지 (출처) 브라우저가 기본으로 만든 스타일, 웹사이트 개발자가 만든 스타일, 사용자가 설정한 스타일이 있어요. 이 중에서 보통은 개발자가 만든 스타일이 가장 강력해요. 마치 학교 규칙(브라우저) < 반 규칙(개발자) < 개인 사정(!important)처럼요.
🥈 두 번째: 얼마나 구체적인지 (명시도) “모든 버튼”이라고 하는 것보다 “빨간색 버튼”이라고 하는 게 더 구체적이죠? CSS도 마찬가지예요. 더 구체적으로 지정한 스타일이 이겨요. 예를 들어 “ID로 지정” > “class로 지정” > “태그 이름으로 지정” 순서로 구체적이에요.
🥉 세 번째: 어떤 게 나중에 나왔는지 (순서) 만약 위의 두 가지가 똑같다면? 그때는 나중에 작성된 스타일이 이겨요. 마치 친구들과 게임 규칙을 정할 때 “어? 그거 아까 바꿨잖아” 하고 최신 규칙을 따르는 것처럼요.
🎲 예시로 이해하기 철수가 “모든 공은 빨간색”이라고 말했어요. 그 다음 영희가 “축구공은 파란색”이라고 말했어요. 마지막으로 민수가 “축구공은 초록색”이라고 말했어요. 축구공은 무슨 색일까요? 답은 초록색이에요! 왜냐하면 영희와 민수가 더 구체적으로(축구공) 말했고, 그 중에서 민수가 나중에(순서) 말했기 때문이에요.
중급
캐스케이드는 세 단계의 비교 과정을 통해 최종 승자를 결정합니다. 각 단계는 순차적으로 실행되며, 한 단계에서 승자가 결정되면 다음 단계는 평가하지 않습니다.
1단계: Origin and Importance (출처와 중요도)
스타일의 출처(어디서 선언되었는지)와 !important 플래그를 확인합니다. 우선순위는 다음과 같습니다:
- Transition declarations (전환 애니메이션)
- Important user agent declarations (
!important브라우저 기본값) - Important user declarations (
!important사용자 설정) - Important author declarations (
!important개발자 스타일) - Normal author declarations (일반 개발자 스타일)
- Normal user declarations (일반 사용자 설정)
- Normal user agent declarations (일반 브라우저 기본값)
/* User Agent (브라우저 기본) */
p { color: black; }
/* Author (개발자 작성) */
p { color: blue; }
<p>이 텍스트는 파란색입니다</p>
<!-- Author 스타일이 User Agent보다 우선 -->
2단계: Specificity (명시도) 첫 번째 단계에서 동일한 Origin을 가진 선언들이 경쟁하면, 선택자의 명시도를 비교합니다. 명시도는 (ID 개수, Class 개수, Type 개수)로 계산됩니다.
p { color: blue; } /* (0,0,1) */
.text { color: red; } /* (0,1,0) - 승리 */
#title { color: green; } /* (1,0,0) - 최종 승리 */
<p class="text" id="title">이 텍스트는 초록색</p>
<!-- ID 선택자가 가장 명시적 -->
3단계: Order of Appearance (소스 순서) Origin과 Specificity가 모두 동일하면, 소스 코드에서 나중에 선언된 규칙이 승리합니다.
.text { color: blue; }
.text { color: red; }
<p class="text">이 텍스트는 빨간색</p>
<!-- 나중에 선언된 red가 승리 -->
심화
캐스케이드의 세 단계는 CSS Cascading Level 5 명세 §6 Cascading에 정의되어 있으며, 각 단계는 서로 다른 복잡도를 가진 비교 알고리즘을 사용합니다.
Origin and Importance 구현 세부사항
Origin 계층은 8단계로 구성되며, 각 계층은 비트마스크로 표현됩니다. Blink 엔진에서는 CascadeOrigin 열거형을 사용하여 다음과 같이 구현합니다:
enum CascadeOrigin {
kTransition = 0,
kUserAgent = 1,
kUser = 2,
kAuthor = 3,
kAnimation = 4,
// Important는 역순으로 처리
};
Important 플래그는 계층을 역전시킵니다. 예를 들어 Important Author는 Normal User Agent보다 높은 우선순위를 가지지만, Important User Agent보다는 낮습니다. 이는 접근성 요구사항을 충족하기 위한 설계입니다 (사용자가 큰 폰트를 강제할 수 있어야 함).
Specificity 계산 알고리즘 명시도는 (A, B, C) 튜플로 표현되며, 각 요소는 무한 진법처럼 비교됩니다:
- A: ID 선택자 개수
- B: Class, Attribute, Pseudo-class 선택자 개수
- C: Type, Pseudo-element 선택자 개수
:where() 의사 클래스는 명시도 0을 가지며, :is() 및 :not()은 인자 중 가장 높은 명시도를 가집니다. CSS Nesting에서는 & 선택자의 명시도가 컨텍스트에 따라 동적으로 계산됩니다.
Order of Appearance 최적화 소스 순서는 선언 인덱스로 추적되며, StyleSheetContents 클래스에서 단조 증가하는 카운터로 관리됩니다:
unsigned declarationIndex = styleSheet->ruleCount++;
동적 스타일 변경 시 인덱스 재계산을 피하기 위해 증분(Incremental) 인덱싱을 사용합니다. CSSOM 조작으로 규칙이 추가되면 기존 인덱스는 유지되고 새 규칙만 증가된 인덱스를 받습니다.
캐스케이드 레이어(Cascade Layers)
CSS Cascading Level 5에서 도입된 @layer는 Origin과 Specificity 사이에 새로운 계층을 추가합니다:
Origin → Cascade Layer → Specificity → Order
레이어는 선언 순서를 무시하고 명시적인 우선순위를 부여할 수 있게 합니다. 이는 O(n²) 복잡도를 가진 Specificity 계산을 O(1) 레이어 조회로 대체할 수 있어 대규모 스타일시트에서 성능 이점을 제공합니다.
스타일 출처의 종류와 우선순위
입문
CSS 스타일은 세 곳에서 올 수 있어요. 각각의 힘이 다르답니다!
🌐 브라우저 기본 스타일 (User Agent) 웹 브라우저가 처음부터 가지고 있는 스타일이에요. 예를 들어 링크는 기본적으로 파란색이고 밑줄이 그어져 있죠? 이건 개발자가 지정하지 않아도 브라우저가 자동으로 적용하는 거예요. 마치 학교에 입학하면 기본적으로 따라야 하는 학칙 같은 거예요.
👨💻 개발자 스타일 (Author) 웹사이트를 만든 개발자가 직접 작성한 스타일이에요. CSS 파일에 적거나 HTML에 직접 쓴 스타일이 여기에 해당해요. 대부분의 경우 이게 가장 강력해요. 마치 담임 선생님이 우리 반만의 특별한 규칙을 만드는 것처럼요.
👤 사용자 스타일 (User)
웹사이트를 보는 사람이 브라우저 설정에서 바꾼 스타일이에요. 예를 들어 시력이 안 좋은 사람이 “모든 글자를 크게 보여줘”라고 설정할 수 있죠. 보통은 개발자 스타일에 밀리지만, !important를 붙이면 가장 강력해져요!
🔥 Important의 마법
!important라는 특별한 키워드를 붙이면 우선순위가 뒤집혀요! 평소엔 개발자 > 사용자 > 브라우저 순서지만, !important를 붙이면 사용자 > 개발자 > 브라우저 순서가 돼요. 이건 접근성을 위한 설계예요. 사용자가 꼭 필요한 설정은 지킬 수 있게 하는 거죠.
🎭 실생활 비유 학교 급식을 생각해보세요. 기본 메뉴(브라우저)가 있고, 영양사 선생님이 특별 메뉴(개발자)를 정하고, 알레르기가 있는 학생은 대체식(사용자 !important)을 받아요. 대체식은 다른 모든 메뉴보다 우선이에요!
중급
CSS 스타일은 세 가지 출처(Origin)에서 선언될 수 있으며, 각 출처는 캐스케이드에서 서로 다른 우선순위를 가집니다.
User Agent Origin (브라우저 기본 스타일) 브라우저가 제공하는 기본 스타일시트입니다. 모든 HTML 요소는 User Agent 스타일을 가지며, 이는 개발자가 스타일을 지정하지 않았을 때 표시되는 기본 모양을 정의합니다.
예시:
<h1>요소는 기본적으로 크고 굵은 글꼴<a>요소는 기본적으로 파란색과 밑줄<button>요소는 기본적으로 3D 버튼 스타일
/* Chrome의 User Agent 스타일 (일부) */
h1 {
display: block;
font-size: 2em;
margin-block-start: 0.67em;
margin-block-end: 0.67em;
font-weight: bold;
}
Author Origin (개발자 작성 스타일)
웹 개발자가 작성한 스타일시트입니다. <link> 태그로 연결된 외부 CSS, <style> 태그 내부의 CSS, 인라인 style 속성이 모두 Author Origin에 속합니다.
일반적으로 Author 스타일은 User Agent 스타일을 덮어씁니다.
/* Author 스타일 */
h1 {
font-size: 3em; /* User Agent의 2em을 덮어씀 */
color: navy;
}
<h1>이 제목은 3em 크기에 navy 색상입니다</h1>
User Origin (사용자 설정 스타일) 웹사이트 방문자가 브라우저 설정이나 브라우저 확장 프로그램을 통해 적용한 스타일입니다. 접근성을 위해 큰 글꼴, 고대비 색상 등을 설정할 수 있습니다.
일반적으로 User 스타일은 Author 스타일에 밀리지만, !important가 붙으면 가장 높은 우선순위를 가집니다.
/* Author 스타일 */
p { font-size: 16px !important; }
/* User 스타일 (브라우저 설정) */
p { font-size: 24px !important; }
<p>이 텍스트는 24px입니다</p>
<!-- User의 !important가 Author의 !important를 이김 -->
심화
CSS Cascading Level 5 명세 §6.1 Cascading Origins에 정의된 출처 시스템은 웹의 다층 스타일 모델을 구현하며, 각 출처는 브라우저 렌더링 엔진에서 별도의 스타일시트 컬렉션으로 관리됩니다.
User Agent Origin의 구조적 역할 User Agent 스타일시트는 HTML 명세의 렌더링 섹션(WHATWG HTML Standard §15 Rendering)에서 정의한 기본 표현(Default Presentation)을 구현합니다.
Blink 엔진에서 User Agent 스타일은 다음과 같이 계층화됩니다:
- html.css: 범용 HTML 요소 스타일
- quirks.css: Quirks Mode 전용 스타일
- view-source.css: 소스 보기 모드 스타일
- 플랫폼별 스타일 (mobile.css, desktop.css)
이들은 컴파일 시점에 바이너리에 임베드되어 파싱 오버헤드를 제거합니다 (약 50KB의 압축된 CSS).
Author Origin의 범위와 우선순위 Author Origin은 여러 출처의 스타일을 포함하며, 다음 순서로 로드됩니다:
<link rel="stylesheet">(선언 순서대로)@import규칙 (중첩 가능)<style>태그 (DOM 순서대로)- 인라인
style속성 (명시도 (1,0,0,0)으로 처리)
인라인 스타일은 기술적으로 Author Origin이지만, 선택자 명시도가 아닌 별도의 “inline specificity”를 가지므로 거의 모든 선택자를 이깁니다. 단, !important는 예외입니다.
User Origin의 접근성 의의
User Origin은 WCAG 2.1 (Web Content Accessibility Guidelines)의 “Text Spacing” 및 “Reflow” 성공 기준을 충족하기 위해 설계되었습니다. !important의 역전 우선순위는 사용자가 다음을 강제할 수 있게 합니다:
- 최소 글꼴 크기 (저시력 사용자)
- 고대비 색상 (색각 이상 사용자)
- 애니메이션 비활성화 (전정 장애 사용자)
브라우저 구현에서 User 스타일은 chrome://settings 또는 확장 프로그램(Stylus, Stylish)을 통해 주입됩니다. Firefox는 userContent.css 파일을 직접 지원합니다.
Transition과 Animation의 특수한 Origin CSS Transitions와 CSS Animations는 별도의 “Transition Origin”과 “Animation Origin”을 가지며, 이들은 캐스케이드 최상위에 위치합니다:
Transition Origin > Important User Agent > … > Normal Author > Animation Origin > Normal User > Normal User Agent
Animation Origin은 Normal Author와 Normal User 사이에 위치하여, 애니메이션이 일반 Author 스타일을 덮어쓰지만 User의 !important에는 밀리도록 합니다.
Performance Implications 출처별 스타일 수집은 O(n) 복잡도를 가지지만, 실제 환경에서는 다음 최적화가 적용됩니다:
- User Agent 스타일: 사전 파싱되어 메모리에 상주 (로딩 시간 0ms)
- Author 스타일: HTTP/2 Server Push로 병렬 로드
- User 스타일: 별도 스레드에서 비동기 로드 (메인 렌더링을 차단하지 않음)
대규모 웹사이트에서 출처 관리 최적화는 초기 렌더링 시간을 10-15% 단축할 수 있습니다.
캐스케이드와 상속의 차이
입문
캐스케이드와 상속은 둘 다 CSS에서 스타일을 결정하는 방법이지만, 완전히 다른 개념이에요!
🎯 캐스케이드: 충돌 해결사 캐스케이드는 “같은 요소에 여러 규칙이 충돌할 때” 어떤 걸 선택할지 정해요. 마치 두 사람이 동시에 “저 공은 내 거야!”라고 주장할 때 심판이 누구 것인지 판정하는 것 같아요. 하나의 요소, 여러 개의 규칙 → 캐스케이드가 하나를 선택!
👨👧👦 상속: 부모에게서 물려받기 상속은 “부모 요소의 스타일을 자식 요소가 물려받는 것”이에요. 충돌이 없어요. 그냥 자연스럽게 흘러내리는 거죠. 마치 부모님의 성을 자식이 물려받는 것처럼요. 부모 요소 → 자식 요소로 스타일이 자동으로 전달!
🤔 언제 쓰이나요? 캐스케이드는 “이미 여러 규칙이 있을 때” 작동해요. 예를 들어 빨간색, 파란색, 초록색 중 어떤 걸 고를지 정하는 거죠. 상속은 “규칙이 없을 때” 작동해요. 예를 들어 색상을 지정 안 했으면 부모 색상을 그대로 쓰는 거예요.
🎨 같이 작동할 때 둘은 함께 작동해요! 먼저 상속으로 부모 스타일을 받아요. 그 다음 직접 지정된 스타일들이 있으면 캐스케이드로 하나를 선택해요. 예를 들어 부모에게서 빨간색을 물려받았는데(상속), 나한테 파란색이 지정되어 있으면(캐스케이드로 결정), 최종적으로 파란색이 돼요!
🏠 실생활 비유 가족의 성씨는 상속이에요. 할아버지 → 아빠 → 나에게 자동으로 전달되죠. 하지만 내 이름은 캐스케이드예요. 할아버지, 할머니, 엄마, 아빠가 각각 다른 이름을 제안하면, 가장 최근에 제안된 이름(또는 가장 강력한 사람의 제안)을 따르게 돼요.
중급
캐스케이드(Cascade)와 상속(Inheritance)은 모두 CSS 스타일을 결정하는 메커니즘이지만, 작동 방식과 목적이 전혀 다릅니다.
캐스케이드: 충돌 해결 알고리즘
- 목적: 하나의 요소에 여러 규칙이 적용될 때 최종 값 결정
- 대상: 동일한 속성에 대한 여러 선언
- 방법: Origin, Specificity, Order를 순차적으로 비교
상속: 부모-자식 전파 메커니즘
- 목적: 명시적 선언이 없을 때 부모 요소의 값 사용
- 대상: 상속 가능한 속성 (
color,font-family등) - 방법: DOM 트리를 따라 자동으로 값 전파
p { color: blue; }
p { color: red; }
<p>빨간색 텍스트</p>
<!-- 캐스케이드: 나중 선언이 승리 -->
body { color: blue; }
/* p에는 color를 지정하지 않음 */
<body>
<p>파란색 텍스트</p>
</body>
<!-- 상속: body의 color를 자동으로 물려받음 -->
실행 순서 CSS 스타일 결정은 다음 순서로 이루어집니다:
- 모든 적용 가능한 선언 수집
- 캐스케이드로 각 속성의 Cascaded Value 결정
- 상속으로 Inherited Value 결정 (Cascaded Value가 없는 경우)
- 최종 Computed Value 계산
body { color: gray; }
.parent { color: blue; }
.child {
color: red;
}
<div class="parent">
<p class="child">빨간색</p>
<p>파란색</p>
</div>
<!-- .child: 캐스케이드로 red 선택
두 번째 p: 상속으로 .parent의 blue 물려받음 -->
심화
캐스케이드와 상속은 CSS 명세의 서로 다른 장(Chapter)에 정의되어 있으며, 브라우저 렌더링 엔진에서 별도의 알고리즘으로 구현됩니다.
명세 기반 차이점 CSS Cascading and Inheritance Level 5는 값 처리 파이프라인을 다음과 같이 정의합니다:
- Declared Values: 스타일시트에 선언된 모든 값
- Cascaded Value: 캐스케이드 알고리즘으로 선택된 값
- Specified Value: Cascaded Value 또는 Inherited Value (우선순위: Cascaded > Inherited > Initial)
- Computed Value: 상대 단위를 절대 단위로 변환
- Used Value: 레이아웃 계산 후 최종 값
- Actual Value: 디바이스 제약 적용 후 값
캐스케이드는 2단계, 상속은 3단계에서 실행되며, 상속은 Cascaded Value가 없을 때만 발동하는 폴백(Fallback) 메커니즘입니다.
상속 가능 속성의 정의 CSS 명세는 각 속성이 상속 가능한지 여부를 명시합니다. 일반적으로:
- 상속 가능: 텍스트 관련 속성 (
color,font-*,line-height,text-*) - 상속 불가: 박스 모델 속성 (
margin,padding,border,width,height)
inherit, initial, unset, revert 키워드는 상속 동작을 명시적으로 제어합니다. unset은 상속 가능 속성이면 inherit처럼, 아니면 initial처럼 동작합니다.
브라우저 엔진 구현 Blink 엔진에서 캐스케이드와 상속은 다음과 같이 분리됩니다:
Cascade Resolution: StyleResolver::ApplyMatchedProperties()에서 실행. MatchedPropertiesCache를 사용하여 동일한 선택자 집합에 대해 결과를 캐싱합니다. O(n) 복잡도이며, n은 매칭된 규칙 수입니다.
Inheritance: StyleResolver::ApplyInheritedOnly()에서 실행. 부모 ComputedStyle에서 상속 가능 속성만 복사합니다. O(m) 복잡도이며, m은 상속 가능 속성 수(약 50개)입니다.
성능 특성 상속은 캐스케이드보다 훨씬 저렴합니다:
- 캐스케이드: 평균 5-10ms (복잡한 페이지에서 수천 개 규칙 비교)
- 상속: 평균 0.5-1ms (단순 메모리 복사)
이는 왜 텍스트 속성이 상속 가능하게 설계되었는지 설명합니다. 모든 텍스트 노드마다 font-family를 명시하면 캐스케이드 비용이 폭증하지만, 상속을 사용하면 루트 요소 하나만 설정하면 됩니다.
CSS Custom Properties의 특수성
CSS Custom Properties(--*)는 항상 상속됩니다:
:root { --color: blue; }
div { color: var(--color); } /* 모든 div가 blue를 상속 */
이는 명시적으로 설계된 동작이며, Custom Properties를 “캐스케이드 변수”가 아닌 “상속 변수”로 만듭니다. Shadow DOM에서 Custom Properties는 Shadow 경계를 넘어 상속되므로, 테마 시스템 구현에 유용합니다.