Cascading and inheritance/Property value processing

김동현·2026년 3월 18일

mdn 학습 번역 - CSS

목록 보기
53/190

안녕하세요! 프론트엔드 개발 공부는 잘 되어가시나요? 우리가 무심코 작성하는 CSS 코드 한 줄(font-size: 1.25em)이 브라우저 화면에 픽셀 단위로 정확하게 그려지기까지, 보이지 않는 곳에서 어떤 엄청난 연산 과정들이 일어나는지 궁금하신 적 없으신가요?

오늘 살펴볼 MDN 문서는 'CSS 속성값 처리 과정(CSS property value processing)'입니다. 면접 단골 질문이기도 하고, 프론트엔드 장인으로 거듭나기 위해 반드시 알아야 하는 심화 내용이죠.

실무에서 "분명 em으로 줬는데 왜 브라우저 검사기엔 px로 찍히지?"라며 고개를 갸우뚱했던 경험을 이 문서를 통해 아주 명쾌하게 해소해 드릴게요!


CSS 속성값 처리 과정 (CSS property value processing)

브라우저는 문서 트리(DOM 트리)에 있는 모든 요소(element)에 대해, 그 요소에 적용될 수 있는 모든 CSS 속성에 값을 할당합니다. 특정 요소나 박스에 최종적으로 렌더링 된 각 CSS 속성값은 그냥 나오는 게 아닙니다. 스타일시트의 정의, 상속(inheritance), 캐스케이드(cascade) 알고리즘, 속성 간의 의존성, 단위 변환, 그리고 현재 화면의 렌더링 환경 등을 모두 종합하여 계산해 낸 피땀 눈물의 결과물이죠.

이 가이드는 각 CSS 값이 최종적으로 어떻게 렌더링 되는지를 정의하는 일련의 처리 과정(processing steps)을 요약해서 보여줍니다. 지정값(specified value), 계산값(computed value), 사용값(used value), 그리고 실젯값(actual value) 같은 핵심 개념들을 탐구해 보면서 말이죠.


이 문서의 내용 (In this article)


속성값 (Property values)

요소(element)나 가상 요소(pseudo-element)에 적용되는 모든 스타일은 단일 CSS 속성 선언을 기반으로 합니다. 하나의 CSS 속성은 오직 하나의 값만 가질 수 있습니다.

어떤 값이 최종적으로 적용될지는 해당 요소에 적용된 모든 속성 선언들의 캐스케이드된 값(cascaded values)을 통해 결정됩니다. 이때, 캐스케이드 알고리즘에 의해 결정된 캐스케이드 정렬 순서(cascade sorting order)에서 가장 높은 순위를 차지하는 단 하나의 속성 선언만이 승리하여 적용됩니다.

똑같은 요소에 대해 같은 속성을 여러 번 작성하거나 다르게 작성해서 선언된 값(declared values)이 여러 개 존재하는 경우에도, 오직 한 개의 속성-값(name-value) 쌍만이 적용됩니다. (그 값이 쉼표로 구분된 여러 개의 목록 형태일지라도, 속성 자체의 값은 하나로 취급됩니다.)

브라우저(사용자 에이전트)는 어떤 선언된 값을 최종적으로 적용할지 결정하기 위해 인라인 스타일, 내부/외부 스타일시트 등 여러 출처(sources)에서 스타일을 긁어모아 처리하는 과정을 거칩니다.

여러분이 작성한 스타일들이 서로 충돌할 때, 캐스케이드(Cascade)는 어떤 값을 적용해야 할지 판결을 내려주는 판사님입니다. 이 알고리즘은 다양한 출처, 스코프, 혹은 레이어(layers)에서 온 속성값들을 어떻게 결합할지 정의합니다. 예를 들어, 명시도(specificity)가 더 높은 선택자가 있더라도, 우선순위가 더 높은 출처(origin)에서 선언된 값이 더 우선시되어 적용됩니다.

일부 속성들(color, font-family 등)은 명시적으로 덮어쓰지 않는 한 부모 요소로부터 값을 상속(inherit)받습니다. 상속(Inheritance)은 요소의 특정 속성에 대한 스타일 정보가 전혀 없을 때 발생하죠. 만약 속성이 상속 가능한 성질을 가졌다면, 부모 요소의 계산값(computed value)을 자신의 값으로 세팅합니다. 반대로 상속 불가능한 속성(border 등)이라면, 해당 속성의 초깃값(initial value)으로 세팅됩니다.

이렇게 단계별로 캐스케이딩(cascading) 규칙을 적용하고 기본값을 세팅하는 과정을 다 마친 후에야, 브라우저는 비로소 우리가 짠 CSS 코드와 똑같은 시각적 결과물을 화면에 그려내게 됩니다.


처리 과정 개요 (Processing overview)

값들이 처리되는 각각의 단계를 깊이 파고들기 전에, 값 처리에 일어나는 3가지 주요 단계인 필터링(filtering), 캐스케이딩(cascading), 기본값 설정(defaulting)을 이해하는 것이 중요합니다.

필터링 (Filtering)

필터링(Filtering)은 각 요소에 실제로 적용될 수 있는 모든 선언들을 걸러내는 과정입니다. 다음 조건들을 모두 만족해야만 살아남을 수 있습니다:

  • 이 문서(HTML)에 현재 연결된 스타일시트에 포함된 선언이어야 합니다.
  • 만약 선언이 @media@supports 같은 조건부 규칙(conditional rules) 안에 있다면, 그 조건이 현재 true(참)여야 합니다.
  • 선언이 속한 선택자(selector)가 해당 요소와 일치(match)해야 합니다.
  • 문법적으로 유효해야 합니다: 브라우저가 속성 이름을 인식할 수 있어야 하고, 값의 형태도 해당 속성이 요구하는 규칙에 맞아야 합니다.

오직 유효한 선언들만이 '선언된 값'으로 인정받습니다. 속성 이름이나 값을 잘못 적은 선언들은 CSS 에러 처리 규칙(CSS error handling rules)에 따라 즉각 필터링되어(버려져) 무시됩니다.

예를 들어 볼까요? 아래 코드에서는 오직 font-sizefont-weight 선언만 정상 처리됩니다. CSS 파서는 colr라는 잘못된 오타 속성을 에러로 취급하여 조용히 필터링해 버립니다.

p {
  font-size: 1.25em;
  colr: blue; /* 오타 발생! 필터링됨 */
  font-weight: bold;
}

필터링 단계가 끝나면, 문서의 모든 요소는 각각의 CSS 속성에 대해 0개 이상의 선언된 값(declared values)들을 가지게 됩니다. 이 값들이 바로 다음 단계인 '캐스케이딩'의 출발점이 되죠.

캐스케이딩 (Cascading)

캐스케이드(Cascade)는 하나의 요소의 동일한 속성을 두고 여러 개의 선언이 서로 싸울 때, 그 충돌을 해결해 주는 규칙입니다. 캐스케이드 정렬 순서(cascade sorting order) 알고리즘을 사용해서 선언들의 랭킹을 매기죠.

예를 들어, <p class="large">CSS is fun!</p>라는 문장이 있다고 칩시다. 아래 두 CSS 규칙은 모두 저 문장에 적용될 수 있지만, 두 번째 선언이 더 높은 명시도(specificity)를 가지고 있기 때문에 최종 승리자가 됩니다.

p {
  font-size: 1em; /* 명시도: 0-0-1 */
}

p.large {
  font-size: 1.5em; /* 명시도: 0-1-1 (클래스가 있어서 더 구체적임!) */
}

이 캐스케이딩 과정이 끝나면, 브라우저는 요소의 각 속성에 대해 단 하나의 캐스케이드된 값(cascaded value)을 결정하게 됩니다. 이 값은 다음 단계인 '기본값 설정'으로 넘어갑니다.

기본값 설정 (Defaulting)

기본값 설정(Defaulting)은 모든 요소의 모든 속성이 반드시 '어떤 값이든' 가질 수 있도록 보장하는 단계입니다. 여러분이 CSS로 명시하지 않은 속성들에 기본값을 채워 넣는 작업이죠. 이 단계는 다음 두 가지 일을 합니다:

이 기본값 설정 과정을 거치고 나면, 모든 CSS 속성은 반드시 하나의 지정값(specified value)을 갖게 됩니다. (여러분이 값을 지정했든, 브라우저가 기본값으로 채워 넣었든 말이죠.)

참고: 여러분이 코드에 명시적으로 적어둔 기본값 키워드들(initial, inherit, unset, revert 등)도 이 단계에서 해석되어 최종 지정값을 결정하는 데 쓰입니다.


처리 단계들 (Processing stages)

문서 트리에 존재하는 모든 요소들은 선언된 값, 캐스케이드된 값, 지정값, 계산값, 사용값, 실젯값을 가지게 됩니다. 특정 속성의 경우 이 값들이 모두 똑같을 수도 있고, 단계를 거치며 달라질 수도 있습니다.

예를 들어, CSS에 p { font-size: 1.25em; }이라고 적어두고 HTML에 <p class="large">CSS is fun!</p>라고 적었다면, 이 문장은 화면에 도대체 몇 픽셀(px) 크기로 그려질까요? 우리가 적은 1.25em이라는 값이 최종 렌더링 될 픽셀(px) 값으로 변환되기 위해, 바로 아래의 처리 단계들을 거치게 됩니다.

1. 선언된 값 (Declared value)

선언된 값이란 요소에 적용할 수 있는 모든 문법적으로 올바른 CSS 선언들을 말합니다. 요소의 특정 속성 하나에 대해 이런 선언이 0개일 수도 있고 수십 개일 수도 있습니다. 이 값들은 작성자(개발자), 사용자, 브라우저 기본 스타일시트 등 다양한 곳에서 가져오며, 앞서 말한 필터링 단계를 통과한 녀석들입니다.

/* 브라우저 기본(User agent) 스타일 */
p {
  font-size: 1em;
}

/* 개발자(Author) 스타일 */
p {
  font-size: 1.25em;
}

.large {
  font-size: 2em;
}

위 예제에서 <p class="large"> 태그에는 세 가지 font-size 선언이 모두 매칭됩니다. 따라서 이 요소의 font-size 속성에 대한 '선언된 값'은 총 3개가 됩니다.

2. 캐스케이드된 값 (Cascaded value)

캐스케이드된 값은 선언된 여러 개의 값들 중에서 캐스케이드 전투(?)에서 최종 승리한 단 하나의 값을 의미합니다.

개발자(Author)가 짠 스타일은 브라우저 기본 스타일보다 무조건 이깁니다. 그리고 같은 개발자 스타일 안에서는 더 구체적인(명시도가 높은) 선택자가 이기게 되죠. 따라서 위 예제에서 캐스케이드된 값은 명시도가 가장 높은 font-size: 2em;이 됩니다.

font-size: 2em;

만약 여러분이 특정 속성에 대해 아무런 CSS도 적지 않아서 선언된 값이 하나도 없다면, 캐스케이드된 값도 없게 됩니다. 이런 경우엔 브라우저가 기본값 설정 과정을 통해 값을 알아서 결정합니다.

3. 지정값 (Specified value)

지정값기본값 설정 과정의 최종 결과물입니다. 이 값은 모든 요소의 모든 속성에 무조건 하나씩 존재합니다. 지정값은 다음 순서로 결정됩니다:

  1. 만약 캐스케이드된 값이 존재한다면, 그 값이 바로 지정값이 됩니다.
  2. 캐스케이드된 값이 없고, 그 속성이 상속 가능한 속성(inherited)이라면, 부모 요소의 계산값을 가져와 지정값으로 씁니다.
  3. 캐스케이드된 값이 없고, 그 속성이 상속 불가능한 속성이라면, 속성 자체의 초깃값(initial value)을 지정값으로 씁니다.

우리 예제의 경우, 캐스케이드된 값 2em이 버젓이 존재하므로 이 값이 그대로 지정값이 됩니다.

font-size: 2em;

만약 지정해 준 값이 아무것도 없을 때는 어떻게 될까요? color 속성은 상속이 되는 성질을 가졌으므로 부모의 계산값을 상속받아옵니다. 반면 margin 속성은 상속되지 않는 성질이므로, 브라우저는 margin의 공식 초깃값인 0을 지정값으로 가져다 씁니다.

color: inherit;
margin: 0;

초깃값 (Initial value)

각 속성의 초깃값은 CSS 명세서에 정해져 있는 기본 디폴트 값입니다. 브라우저가 값을 못 찾아서 [기본값 설정]을 해야 할 때 아래와 같이 사용됩니다.

  • 상속되는 속성의 경우: 부모가 없는 문서의 최상위 요소(root element, <html>)에 한해서 초깃값이 쓰입니다.
  • 상속 안 되는 속성의 경우: 값을 찾지 못한 모든 요소에 일괄적으로 초깃값이 쓰입니다.

참고: 초깃값은 브라우저가 기본적으로 먹여두는 "브라우저 기본 스타일시트(User agent stylesheet)"와는 다릅니다. 속성 자체의 근본적인 디폴트 값이라고 생각하시면 됩니다.

4. 계산값 (Computed value)

계산값은 요소가 부모로부터 상속을 받을 때 실제로 자식에게 물려주는 값입니다. 지정값에서 em이나 % 같은 상대적인 단위를 어느 정도 절대적인 수치로 풀어낸 값이죠. 하지만 화면의 레이아웃이 결정되기 전의 상태입니다.

계산값은 지정값으로부터 다음과 같이 도출됩니다:
1. inherit, initial, unset 같은 특별한 키워드들을 처리합니다.
2. 명세서의 "계산값(Computed value)" 항목에 정의된 방식대로 필요한 수학적 연산을 수행합니다.

보통 이 단계에서 상대적인 값(em이나 %)이 절대적인 값(px 등)으로 변환됩니다. 예를 들어 지정값이 font-size: 16px이고 padding-top: 2em이라면, 이 단계에서 브라우저는 "음, 폰트가 16px이니까 패딩은 그 두 배인 32px이겠군!" 하고 padding-top의 계산값을 32px로 확정 짓습니다.

하지만 width, margin-right, top처럼 부모 요소의 넓이나 높이에 따라 유동적으로 변하는 퍼센트(%) 값들은, 아직 화면 레이아웃이 계산되기 전이기 때문에 이 단계에서 퍼센트 값을 그대로 유지합니다. 이런 퍼센트 값들은 다음 단계인 사용값 단계에서야 비로소 절대적인 수치로 풀리게 됩니다.

💡 강사 팁:
"부모의 값을 상속받는다"고 할 때 자식이 물려받는 것이 바로 이 '계산값'입니다. 만약 부모 글씨 크기가 16px인데, 부모의 패딩이 2em이었다면 자식은 2em을 물려받는 게 아니라 이미 계산이 끝난 32px을 물려받게 됩니다. 아주 중요한 포인트입니다!

5. 사용값 (Used value)

사용값은 계산값에 최종적인 레이아웃 연산까지 모두 끝난 뒤의 값입니다. (예: 살아남은 퍼센트 값들이 화면의 실제 픽셀 크기에 맞춰 최종 변환된 상태)

모든 CSS 속성은 이 사용값을 가집니다. widthline-height 같은 치수들은 이 단계에서 모두 완벽한 px 픽셀 단위로 고정됩니다. 즉, CSS 파일에 너비를 50%로 주었든 auto로 주었든 간에, 이 단계에 오면 무조건 "너비: 320px"처럼 픽셀 숫자로 확정됩니다.

예를 들어, 세 개의 <div> 너비를 각각 auto, 50%, inherit으로 주었다고 가정해 봅시다.

<div id="no-width">
  <p>No explicit width.</p>
  <p class="show-used-width">..</p>
  <div id="width-50">
    <p>Explicit width: 50%.</p>
    <p class="show-used-width">..</p>
    <div id="width-inherit">
      <p>Explicit width: inherit.</p>
      <p class="show-used-width">..</p>
    </div>
  </div>
</div>

우리가 코드에 적은 '지정값'들은 퍼센트이거나 키워드지만, 자바스크립트로 window.getComputedStyle(el)["width"]를 찍어서 값을 가져와 보면 전부 브라우저가 화면에 그리기 위해 픽셀로 변환해 둔 '절대 길이(px)'가 반환됩니다. 이 픽셀 값이 바로 사용값(Used value)과 밀접하게 연관되어 있습니다.

화면을 늘렸다 줄였다 해보세요. 자바스크립트가 읽어오는 픽셀 값이 실시간으로 변하는 걸 보실 수 있습니다.

MDN Playground에서 실행해보기 (Live Sample)


렌더링 된 값 (Rendered values)

최종적으로 브라우저 화면에 렌더링 되는 값을 실젯값(actual value)이라고 부르며, 자바스크립트로 우리가 읽어오는 값을 해결된 값(resolved value)이라고 부릅니다.

실젯값 (Actual value)

실젯값은 방금 구한 사용값(used value)을 모니터나 브라우저의 물리적인 한계에 맞게 아주 미세하게 보정한 최종 값입니다.

예를 들어, 브라우저가 테두리 두께를 정수 픽셀로만 그릴 수 있는데 사용값이 1.3px로 계산되었다면, 브라우저는 이것을 반올림해서 1px로 렌더링 해버릴 수 있습니다. 이 반올림된 최종 결과물이 바로 실젯값입니다.

전체 여정을 요약하자면 이렇습니다:
1. 선언된 스타일들을 종합해 지정값을 정한다.
2. 상대 단위를 풀어서 계산값을 만든다.
3. 화면 레이아웃 사이즈를 반영해서 픽셀로 바꾼 사용값을 만든다.
4. 모니터 성능에 맞게 소수점 등을 쳐내서 최종 실젯값을 만든다.

해결된 값 (Resolved value)

해결된 값은 자바스크립트의 getComputedStyle() 메서드를 호출했을 때 브라우저가 우리에게 던져주는 값을 말합니다.

이름이 get"Computed"Style이라서 무조건 '계산값(computed value)'을 줄 것 같지만, 옛날 브라우저들과의 호환성 때문에 속성에 따라 계산값을 주기도 하고, 레이아웃 처리가 모두 끝난 사용값(used value)을 주기도 합니다. 이것을 뭉뚱그려서 "해결된 값"이라고 부릅니다.

대부분의 속성은 계산값을 던져주지만, 레이아웃에 직접적인 영향을 받는 속성들(width, height, margin, padding 등)은 자바스크립트로 읽어오면 계산값이 아닌, 최종적으로 픽셀로 풀이된 '사용값(used value)'을 반환해 줍니다.

💡 강사 팁:
"선생님, 자바스크립트로 .style.width를 찍었는데 아무 값도 안 나와요!" 라는 질문이 정말 많습니다. 자바스크립트의 .style 객체는 인라인 스타일만 읽어옵니다. CSS 파일에 적힌 진짜 적용된 너비(픽셀)를 알고 싶다면, 반드시 window.getComputedStyle(요소).width를 사용하셔야 합니다. 이때 브라우저가 돌려주는 픽셀 값이 바로 지금 배운 '해결된 값(Resolved value)'이랍니다!


참고 자료 (See also)


MDN 개선에 참여하기 (Help improve MDN)

이 페이지가 도움이 되셨나요?
[Yes][No]

기여하는 방법 알아보기(Learn how to contribute)
이 페이지의 마지막 수정일은 2026년 1월 6일이며, MDN 기여자들에 의해 수정되었습니다.


어떠셨나요? 겉보기엔 단순해 보였던 CSS가 뒤에서는 이렇게 치밀하고 체계적인 4~5단계의 필터링을 거쳐 화면에 출력된다는 사실이 놀랍지 않나요?

이 원리를 이해해 두시면 앞으로 반응형 작업을 하거나, 자바스크립트로 DOM의 크기를 제어하는 로직을 짤 때(예를 들어 스크롤 애니메이션 같은 것들) 픽셀 값이 어긋나서 생기는 버그를 아주 빠르고 정확하게 잡아내실 수 있을 겁니다. 화이팅입니다!

profile
프론트에_가까운_풀스택_개발자

0개의 댓글