CSS 강좌 / 상속·우선순위·캐스케이드로 최종 스타일 결정하기
CSS의 최종 스타일은 각 요소의 속성별로 적용 후보를 비교한 뒤 결정됩니다. 상속은 부모의 값을 물려받는 과정이고, 캐스케이드(cascade)는 여러 선언 중 어떤 선언을 사용할지 정하는 과정입니다. 같은 출처·중요도·레이어 조건에서는 선택자 우선순위를 비교하고, 그것도 같을 때 작성 순서를 비교하므로 “맨 아래 CSS가 항상 이긴다”는 설명은 정확하지 않습니다.
상속과 직접 지정부터 구분하기
상속은 자식 요소가 부모 요소의 값을 물려받는 것입니다. 글자색 color나 글꼴 관련 속성은 일반적으로 상속되는 반면, 테두리 border와 안쪽 여백 padding은 기본적으로 상속되지 않습니다. 모든 속성이 부모와 같아진다고 생각하면 문단에 예상하지 못한 테두리를 찾게 될 수 있습니다.
#main {
color: purple;
border: 1px solid purple;
}
p { color: #555; }
<main id="main"> <h1>작은 정원</h1> <p>방문 안내입니다.</p> </main>
이 두 조각만 적용한다고 가정하면 h1은 부모 main의 보라색을 물려받습니다. p에는 직접 지정한 회색이 적용됩니다. 부모의 #main이 ID 선택자라는 이유로 자식 p의 직접 지정값을 이기지는 않습니다. 서로 같은 요소에 적용되는 두 선택자의 경쟁이 아니기 때문입니다. 테두리는 main의 상자 주위에 생기며 h1과 p 각각의 테두리로 복제되지 않습니다.
상속되지 않는 속성도 border: inherit처럼 명시하면 부모의 계산된 값을 물려받을 수 있습니다. 반대로 상속 속성이라도 해당 요소에 적용되는 선언이 있으면 그 선언을 먼저 처리합니다. 어떤 속성이 상속되는지와 inherit의 의미는 MDN의 CSS 상속 설명에서 확인할 수 있습니다.
최종 선언을 판단하는 순서
처음에는 본문에 작성한 CSS, 즉 같은 작성자 출처의 일반적인 스타일을 중심으로 아래 순서를 사용하세요. 먼저 선택자가 해당 요소에 일치하고 문법·조건이 유효한지 확인합니다. 대상이 아닌 규칙이나 잘못된 값은 우선순위 계산에 넣기 전에 제외해야 합니다.
- 출처와 중요도, 레이어 등의 상위 조건을 확인합니다. 작성자 CSS와 사용자 CSS·브라우저 기본 스타일의 출처, !important 여부, @layer 사용 여부 등이 여기 관련됩니다. 상위 조건이 다르면 선택자의 점수만 비교해서 결정하지 않습니다.
- 같은 조건 안에서 선택자 우선순위를 비교합니다. 우선순위(specificity)는 선택자가 얼마나 구체적인지 비교하는 기준입니다. ID, 클래스 계열, 요소 계열을 순서대로 비교합니다.
- 우선순위까지 같으면 뒤에 작성된 선언을 사용합니다. 이 강의의 예제처럼 @scope 등의 추가 조건이 없는 상황에서는 최종 동률을 소스 순서로 해결합니다.
- 선언으로 정해지지 않은 속성은 기본값 처리 과정을 따릅니다. 상속되는 속성은 부모에게서 값을 물려받고, 그렇지 않은 속성은 초기값 등을 사용합니다.
캐스케이드는 CSS의 전체 충돌 해결 체계이고, 선택자 우선순위는 그 안의 한 기준입니다. 애니메이션·전환, 레이어, 스코프를 사용하는 고급 상황에는 추가 규칙이 있으므로 여기의 단순 예제를 전체 규칙으로 확대하지 마세요. 출처와 중요도·레이어의 관계는 W3C의 캐스케이드 정렬 규칙에 정의되어 있습니다. 아래 전체 예제에는 애니메이션·전환·@layer·@scope를 사용하지 않습니다.
선택자 우선순위는 세 열로 비교하기
이 강의의 기본 선택자는 ID 수, 클래스·속성·가상 클래스 수, 요소·가상 요소 수의 세 열로 읽을 수 있습니다. 왼쪽 열부터 비교하고, 차이가 나면 그 결과로 결정합니다. 단순히 ID=100, 클래스=10, 요소=1을 더하는 십진수 점수표로 외우지 마세요. 클래스가 많이 모였다고 ID 열을 넘어서는 방식이 아닙니다.
| 선택자 | ID | 클래스·속성·가상 클래스 | 요소·가상 요소 | 표기 |
|---|---|---|---|---|
p |
0 | 0 | 1 | 0-0-1 |
.notice |
0 | 1 | 0 | 0-1-0 |
p.notice |
0 | 1 | 1 | 0-1-1 |
[required] |
0 | 1 | 0 | 0-1-0 |
a:hover |
0 | 1 | 1 | 0-1-1 |
p::before |
0 | 0 | 2 | 0-0-2 |
#opening |
1 | 0 | 0 | 1-0-0 |
#main .notice |
1 | 1 | 0 | 1-1-0 |
p.notice와 .notice는 ID 열이 같고 클래스 열도 같습니다. 요소 열에서 1과 0의 차이가 나므로 p.notice가 높습니다. #opening과 p.notice는 첫 열에서 1과 0의 차이가 나므로 #opening이 높습니다. 공백, >, +, ~ 같은 관계 표기와 전체 선택자 *는 이 비교의 수를 직접 늘리지 않습니다.
p.notice { color: #087f5b; }
.notice { color: tomato; }
같은 p.notice에 두 규칙이 적용되면 뒤의 .notice보다 앞의 p.notice가 높은 우선순위를 가져 초록색이 됩니다. 순서만 뒤로 옮긴다고 결과가 바뀌지 않습니다. 반면 .notice 규칙끼리 같은 속성을 두 번 지정하면 우선순위가 같으므로 뒤의 선언이 이깁니다. 이 계산은 같은 출처·중요도·레이어 조건이라는 전제에서 사용하는 것이며, 특수한 가상 클래스에는 별도 계산 규칙이 있습니다. 자세한 예외는 MDN의 선택자 우선순위 설명을 참고하세요.
속성별로 다른 선언이 이길 수 있습니다
.notice {
color: blue;
padding: 12px;
border: 1px solid #087f5b;
}
.notice {
color: tomato;
padding: 20px;
}
이 두 규칙만 같은 요소에 적용하면 color와 padding은 뒤의 선언을 사용하고, border는 앞 규칙의 선언이 남습니다. 뒤의 규칙에 border가 없다고 앞 규칙 전체가 사라지는 것은 아닙니다. CSS는 “규칙 블록 하나를 통째로 선택”하는 방식보다 “속성마다 값을 결정”하는 방식으로 이해해야 합니다.
예를 들어 글자색은 ID 규칙, 여백은 뒤의 클래스 규칙, 테두리는 앞의 클래스 규칙에서 올 수 있습니다. 개발자 도구에서 취소선을 볼 때도 선언 하나씩 읽으세요. color에 취소선이 있다고 같은 블록의 padding까지 적용되지 않는 것은 아닙니다.
전체 예제를 먼저 예측하고 실행하기
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>작은 정원</title>
<style>
body { margin: 24px; font-family: sans-serif; line-height: 1.7; }
#main { color: purple; }
p { color: #555; }
.notice {
color: blue;
padding: 12px;
border: 1px solid #087f5b;
}
p.notice { color: #087f5b; }
.notice { color: tomato; padding: 20px; }
#opening { color: #1d4ed8; }
</style>
</head>
<body>
<main id="main">
<h1>작은 정원</h1>
<p id="opening" class="notice">A: 매일 오전 9시에 문을 엽니다.</p>
<p class="notice">B: 물병과 편한 신발을 준비하세요.</p>
<p>C: 안내 데스크에서 문의하세요.</p>
</main>
</body>
</html>
브라우저에서 열기 전에 A·B·C의 색과 안내문 여백을 예측해 보세요. 전체 코드를 index.html로 저장해 열면 다음 결과가 나옵니다. body의 글꼴과 줄 높이는 보기 편하게 하는 보조 설정입니다.
| 대상과 속성 | 최종 결과 | 결정 이유 |
|---|---|---|
| h1의 color | 보라색 purple | main의 color를 상속합니다. h1에 다른 color 선언이 없습니다. |
| A의 color | 파란색 #1d4ed8 | #opening이 같은 A에 적용된 p.notice, .notice, p보다 높은 우선순위를 갖습니다. |
| B의 color | 초록색 #087f5b | p.notice가 더 뒤의 .notice보다 높은 우선순위를 갖습니다. |
| C의 color | 회색 #555 | p의 직접 지정값을 사용하며 부모의 보라색을 그대로 상속하지 않습니다. |
| A·B의 padding | 20px | 두 .notice 선언의 우선순위가 같아 뒤의 padding이 이깁니다. |
| A·B의 border | 1px 초록색 실선 | 뒤 규칙에 border 선언이 없으므로 앞의 유효한 선언이 남습니다. |
Computed에서 확인하면 A의 color는 rgb(29, 78, 216), B의 color는 rgb(8, 127, 91), C의 color는 rgb(85, 85, 85)로 표시될 수 있습니다. 색상 표기가 달라도 같은 값을 가리킵니다. padding은 각 방향이 20px인지, border는 두께와 선 모양이 그대로인지 함께 확인하세요.
important와 인라인 스타일의 비교 범위
!important는 선언의 중요도를 바꾸는 표기입니다. 선택자의 세 열에 큰 숫자를 더하는 기능이 아닙니다. 같은 작성자 출처에서는 important 선언이 일반 선언보다 우선하므로 아래 규칙을 전체 예제 끝에 추가하면 A와 B의 색이 모두 토마토색으로 바뀝니다. ID의 일반 color 선언도 이 important 선언보다 우선하지 않습니다.
.notice { color: tomato !important; }
이 결과가 important가 언제나 모든 스타일보다 강하다는 뜻은 아닙니다. 사용자 important 스타일이나 전환 중의 값 등은 별도의 순서를 따릅니다. important 선언끼리도 출처와 레이어 등을 먼저 확인해야 합니다. 이 실습에서는 important를 관찰한 뒤 추가한 규칙을 지우세요. 보통은 선택 범위와 구조를 먼저 정리하는 편이 유지보수에 좋습니다.
<p class="notice" style="color: purple;">B: 준비물 안내</p>
important 규칙을 지운 원래 예제에서 B에 위처럼 인라인 color를 넣으면 일반 스타일시트 규칙보다 우선하여 B가 보라색이 됩니다. 인라인 스타일은 위 표의 기본 선택자 세 열과 별도로 취급합니다. 다시 일반 클래스 규칙에 important를 넣으면 인라인의 일반 color보다 우선할 수 있습니다. 따라서 “인라인이면 무조건 가장 강하다”도 조건이 빠진 설명입니다.
충돌을 해결하는 실습과 확인 순서
- 원본 전체 예제에서
p.notice규칙을 맨 아래로 옮깁니다. B는 여전히 초록색이고 A는 ID의 파란색을 유지하는지 확인합니다. 높은 우선순위가 있는 상태에서는 순서 변경만으로 뒤집히지 않습니다. - 그 다음 p.notice 선택자만
.notice로 바꿔 가장 아래에 둡니다. B의 색은 마지막 .notice가 지정한 초록색을 사용합니다. 같은 선택자가 되었기 때문에 이제 순서가 결정 기준입니다. 확인 후 원본을 복원합니다. - A의
id="opening"만 제거합니다. A도 B와 같은 초록색이 되는지 봅니다. 부모 main의 ID는 그대로 있어도 A에 직접 지정된 초록색을 이기지 않습니다. 확인 후 ID를 복원합니다. - 별도 단계로 위 important와 인라인 예제를 하나씩 적용해 비교합니다. 이전 실습 규칙을 남겨 두지 말고 원본으로 돌아간 뒤 다음 조건을 넣습니다.
결과가 예상과 다르면 개발자 도구에서 Styles와 Computed를 확인하세요. 대상 요소를 정확히 선택하고, 해당 속성의 선언을 찾고, 중요도와 선택자·순서를 비교한 뒤 최종 값을 확인합니다. 원인을 확인하지 않고 ID나 important를 계속 추가하면 당장의 색은 바뀌어도 다음 수정은 더 어려워질 수 있습니다.










