🎨 Frontend CSS1. Cascade & Specificity03 — Inheritance & Value Stages

03 — Inheritance & Value Stages

한 줄 답: 상속(inheritance)은 cascade의 다음 단계다 — cascade가 어떤 값이 선언되었나를 정하면, inheritance는 선언이 없을 때 부모 값을 가져다 쓸지를 정한다. 그리고 그 값은 specified → computed → used → actual의 4단계를 거쳐 화면에 도달한다.


Why — 왜 상속이 필요한가

<article>
  <h1>제목</h1>
  <p>본문 <em>강조</em></p>
</article>

여기서 article { color: navy }만 쓰면 h1, p, em 모두 navy가 된다. 부모에 한 번 쓰면 자식에 자동 적용되는 게 inheritance다.

이게 없다면 모든 요소에 색을 일일이 써야 한다. CSS의 cascade 정신 자체가 이 자동 전파를 전제로 한다 — cascade라는 단어가 “폭포처럼 흘러내림”인 이유.

하지만 모든 속성이 상속되는 건 아니다. border가 상속되면 부모 박스에 border를 그리는 순간 모든 자식에 border가 생긴다 — 재앙. 그래서 명세는 속성별로 기본 상속 여부를 정해뒀다.


How — 상속의 4단계 가치 흐름

1) 값의 4 단계 (CSS Values 4)

브라우저가 한 속성 값을 결정하는 과정:

선언된 값 (declared)
   ↓ cascade
지정된 값 (specified)
   ↓ inherit/initial resolution
계산된 값 (computed)
   ↓ context (font-size, vw 등 해결)
사용된 값 (used)
   ↓ device clamping (반올림, 픽셀 그리드)
실제 값 (actual)
단계의미예: width: 50% (부모 800px)
specifiedCSS에 쓴 그대로50%
computed절대화 가능한 만큼50% (부모를 모를 수 있어 그대로 유지)
used레이아웃 후 실제 픽셀400px
actual장치에 그릴 픽셀400px (서브픽셀 반올림)
단계예: font-size: 1.5em (부모 16px)
specified1.5em
computed24px (font-size는 computed에서 즉시 절대화)
used24px
actual24px

중요: 상속되는 것은 computed value다. 자식이 부모의 font-size: 1.5em을 상속받으면, 1.5em이 아니라 부모의 computed 24px를 받는다 — 그래서 손자가 또 1.5em이면 36px이 된다(누적).

2) 상속되는 속성 vs 상속되지 않는 속성

기본 상속 ✓:

  • 텍스트 관련: color, font-*, line-height, letter-spacing, text-align, text-indent, text-transform, white-space, word-spacing, direction
  • 리스트: list-style
  • 테이블: border-collapse, border-spacing, caption-side
  • 음성: voice-*, cursor
  • 가시성: visibility (단, display는 아님)
  • 모든 custom property (--*)

기본 상속 ✗:

  • 박스 모델: margin, padding, border, width, height
  • 배경: background-*
  • 위치: position, top/right/bottom/left, z-index
  • display, float, clear
  • transform, opacity, filter
  • animation, transition

3) 강제 상속/초기화 키워드 (CSS-wide values)

모든 속성에 쓸 수 있는 4(+1)개의 키워드:

키워드동작
initial명세의 initial value로 (예: colorcanvastext)
inherit부모의 computed value를 강제로 가져옴 (상속 안 되는 속성도 가능)
unset상속되는 속성은 inherit, 아니면 initial
revert이 origin을 무시 → 더 약한 origin(UA 기본)로
revert-layer이 layer를 무시 → 더 약한 layer로
button {
  all: unset;            /* 모든 속성 초기화 */
  font: inherit;          /* 폰트만 부모와 동일 */
  cursor: pointer;
}

all: unset은 버튼/링크 reset의 표준 패턴이다.

revert-layer@layer 시대의 핵심. layer에서 적용한 스타일을 그 layer 안에서만 무효화해 더 낮은 layer의 값으로 돌릴 수 있다.


What — 자주 보는 케이스

케이스 1: color는 상속, background-color는 안 됨

body { color: navy; background-color: #fafafa; }

자식 텍스트는 모두 navy(상속). 하지만 자식의 배경은 각자 transparent(기본값). 그래서 body에 회색을 깔아도 자식 div들은 자기 배경을 갖지 않는다 — 그런데 시각적으로 부모 회색이 비쳐 보일 뿐.

이건 상속이 아니라 transparent 기본값이라는 걸 헷갈리면 안 된다.

케이스 2: font-size 누적

.outer { font-size: 1.5em; }
  .middle { font-size: 1.5em; }
    .inner { font-size: 1.5em; }

html { font-size: 16px } 가정 시:

  • .outer → 24px
  • .middle → 36px (24 × 1.5)
  • .inner → 54px (36 × 1.5)

em상속된 부모의 computed font-size에 곱해진다. 누적을 피하려면 rem(root em — html의 font-size 기준).

케이스 3: custom property는 항상 상속

:root { --brand: oklch(60% 0.2 250); }
button { background: var(--brand); }
.dark { --brand: oklch(40% 0.2 250); }    /* 이 안에서 redefine */

custom property는 항상 상속된다. 그래서 다크모드 전환의 핵심 도구.

.dark button {
  background: var(--brand);    /* 자동으로 .dark의 --brand 사용 */
}

케이스 4: inherit으로 상속 안 되는 속성 강제 전달

.card {
  border: 1px solid currentColor;
}
.card * {
  border-color: inherit;        /* border는 상속 안 되지만, color는 됨 */
}

또는:

.dialog { background: white; }
.dialog * { background: inherit; }   /* 자식 모두 강제로 white 배경 */

(실제로는 거의 안 쓴다 — 위험. 하지만 표현은 가능.)

케이스 5: revert-layer로 layer만 풀기

@layer base, theme;
 
@layer base {
  button { padding: 8px 16px; background: gray; }
}
@layer theme {
  button { background: blue; }
  .reset-theme {
    background: revert-layer;    /* base의 gray로 돌아감 */
  }
}

케이스 6: animation 중의 inheritance

.parent { color: red; }
.parent { animation: c 1s; }
@keyframes c {
  to { color: blue; }
}
.child {
  /* 부모의 *animation-active* color를 상속 */
  /* 초당 red → blue로 변하는 동안 child도 같이 변한다 */
}

자식은 부모의 현재 computed color를 상속한다 — animation 중에는 그 값이 매 프레임 바뀐다.


What-if — 자주 깨지는 패턴

사고 1: form 안의 폰트가 안 따라옴

<body style="font-family: 'Pretendard'">
  <input type="text">
</body>

input은 폰트가 상속되지 않는다 — Form 컨트롤은 UA가 자기 폰트(-webkit-small-control 등)를 강제로 박는다. 해결:

input, button, select, textarea {
  font: inherit;
}

이게 normalize.css의 단골 항목인 이유.

사고 2: 다크모드 전환 시 일부 색만 바뀜

:root { --text: black; }
.dark { --text: white; }
 
h1 { color: black; }              /* 하드코딩 — 안 바뀜 */
p { color: var(--text); }          /* 토큰 — 바뀜 */

color: var(--text)로 일관되게 쓰지 않으면 다크모드가 깨진다. 변수가 상속되어 자동 전파되는 걸 활용해야 토큰 전략이 성립.

사고 3: line-height: 1.5em vs line-height: 1.5

body { font-size: 16px; line-height: 1.5em; }   /* computed: 24px — 그대로 상속 */
h1 { font-size: 32px; }                          /* line-height는 여전히 24px → 좁음! */
body { line-height: 1.5; }                       /* unitless — 매번 자기 font-size × 1.5 */
h1 { font-size: 32px; }                          /* line-height = 48px — 정상 */

unitless line-height만 써라. 단위가 붙으면 computed value로 절대화되어 상속 시 자식의 font-size 변화를 못 따라온다.

사고 4: width: inherit의 함정

.parent { width: 50%; }
.child { width: inherit; }    /* "50%" 그대로 — 부모 width의 50%가 아님 */

width는 상속되지 않는 속성이라 inherit을 명시해야 하는데, computed value가 상속된다 — 즉 50%라는 문자가 그대로 자식에 박힌다. 자식은 *자기 부모(=.parent)의 50%*가 되므로 결과적으로 손주의 50%가 .parent의 50%의 50%가 아니라 .parent의 50%. 헷갈린다. 그래서 거의 안 쓴다.


Insight — revert 키워드와 캐스케이드의 반전

revert(2020)와 revert-layer(2022)는 cascade에 역방향 화살표를 추가한 사건이다.

전통적으로 cascade는 위에서 아래로 — 더 강한 규칙이 약한 규칙을 덮는 단방향이었다. 그래서 “라이브러리가 박은 스타일을 풀고 UA 기본으로 돌아가고 싶다”는 덮어쓰기로만 가능했다. 즉, 적절한 기본값을 찾아 다시 쓰는 수밖에.

/* 옛 방식 — UA 기본을 모르면 못 씀 */
button { padding: 2px 6px; background: buttonface; ... }

revert는 이걸 **“이 origin을 그냥 무시해줘”**라는 선언적 풀기로 바꿨다.

/* 모던 */
button { all: revert; }    /* UA 기본으로 깨끗하게 */

이는 cascade를 *“앞으로 쌓는 스택”*에서 *“각 layer가 켜고 끌 수 있는 OFF 스위치 가진 스택”*으로 진화시켰다. @layer + revert-layer의 조합은 디자인 시스템에서 **“이 컴포넌트는 디자인 토큰을 안 받겠다”**를 한 줄로 표현 가능하게 한다.


요약 + Mermaid

  • 상속은 cascade의 다음 단계다. cascade가 선언된 값을 정하면 inheritance가 없을 때 부모 값을 가져온다.
  • 값은 4단계: specified → computed → used → actual.
  • 상속되는 것은 computed value.
  • 텍스트 관련은 대부분 상속, 박스/배경/위치는 안 됨. custom property는 항상 상속.
  • 5개 키워드: initial, inherit, unset, revert, revert-layer. all: unset은 reset 핵심.

다음: 04-cascade-layers에서 @layer를 완전 정복한다.