한 줄 요약 — 같은 요소에 여러 스타일이 동시에 닿을 때 무엇이 이길지는 두 단계로 정해집니다. 먼저 **명시도(specificity)**가 더 높은 쪽이 이깁니다 — 인라인 스타일 > ID > 클래스·가상 클래스 > 태그 순서로 점수가 매겨집니다. 명시도가 같다면 캐스케이드(cascade), 즉 같은 출처 안에서는 CSS 파일에서 더 아래에 적힌 줄이 이깁니다.
!important는 이 규칙을 전부 뒤집는 비상 카드라서, 한 번 쓰면 그 다음부터는 디버깅이 헝클어집니다.
학습 목표
- 인라인·ID·클래스·태그의 명시도 점수를 네 자리 숫자로 계산할 수 있다.
- 명시도가 같을 때 캐스케이드(코드 위치) 순서가 어떻게 작동하는지 안다.
- 인라인 스타일을 외부 CSS로 덮으려 할 때 왜 잘 안 되는지, 어떻게 풀어야 하는지 안다.
!important가 왜 마지막 수단이어야 하는지 설명할 수 있다.
오늘의 비유 — 회사의 결재 라인, 더 윗선이 더 강하다
같은 안건을 두고 사원·대리·팀장·임원이 서로 다른 의견을 결재 칸에 올렸다고 생각해 봅니다. 사원이 "A안으로 진행"이라고 적었더라도 팀장이 그 위에 "B안으로 진행"이라고 도장을 찍으면 B안이 됩니다. 임원이 더 위에 "C안"이라고 도장을 찍으면 C안이 됩니다. 사장님까지 올라가 "D안"이라고 못 박으면 그 안건은 끝입니다. 윗선의 도장이 아래의 결정을 덮는 것, 이게 CSS의 명시도와 정확히 같은 구조입니다.
같은 직급끼리 의견이 갈리면 회사 규칙은 보통 마지막에 결재한 사람의 의견을 따릅니다. 팀장 두 명이 차례로 도장을 찍었다면 두 번째 팀장의 안이 이깁니다. CSS에서도 명시도가 같으면 나중에 적힌 줄, 즉 캐스케이드의 아래에 적힌 규칙이 이깁니다.
거기에 더해, 어디서도 막을 수 없는 비상 도장이 하나 있습니다 — "이 안건은 무조건 X안으로 간다"는 사장 직권 같은 표시, 그게 !important입니다. 너무 자주 쓰면 누구의 도장이 진짜 결정권을 가졌는지 아무도 알 수 없게 되니, 정말 마지막 수단으로만 꺼냅니다.
핵심 개념
명시도 — 네 자리 점수로 매기는 권한
명시도는 (인라인, ID, 클래스/가상클래스/속성, 태그/가상요소)의 네 자리 점수로 셉니다. 자리마다 점수가 다르고, 같은 자리에 여러 개가 섞이면 그 자리끼리 더합니다.
/* 인라인 스타일: 1, 0, 0, 0 */
/* #header 0, 1, 0, 0 */
/* .btn 0, 0, 1, 0 */
/* :hover 0, 0, 1, 0 */
/* [type=text] 0, 0, 1, 0 */
/* p 0, 0, 0, 1 */
/* ::before 0, 0, 0, 1 */
/* * 0, 0, 0, 0 */
한 선택자에 여러 종류가 같이 섞이면 각 자리를 그냥 더합니다.
ul li.menu a:hover { color: red; }
/* 클래스 1 + 가상클래스 1 = 셋째 자리 2,
태그 3개(ul, li, a) = 넷째 자리 3
→ 0, 0, 2, 3 */
비교는 왼쪽 자리부터 큰 쪽이 즉시 이깁니다. 0, 1, 0, 0(ID 한 개)은 0, 0, 9, 0(클래스 아홉 개)보다 항상 강합니다. 아래 자리가 아무리 커도 위 자리를 못 넘기 때문입니다. 사원 9명이 모인다고 팀장 한 명의 결재를 뒤집을 수는 없는 셈입니다.
캐스케이드 — 같은 점수면 아래 줄이 이긴다
명시도가 똑같이 나오면 다음 단계로 넘어갑니다 — CSS 코드에서 더 뒤에 적힌 줄이 이깁니다.
.btn { color: blue; }
.btn { color: red; }
둘 다 명시도 0, 0, 1, 0이지만, 뒤에 적힌 red가 적용됩니다. 같은 직급의 두 결재 중 나중 도장이 이기는 것과 같습니다.
여러 파일을 <link>로 불러올 때도 같은 원리가 적용됩니다 — 나중에 불러온 파일이 더 아래에 적힌 셈이 됩니다. <head>에서 reset.css를 먼저, 그 다음에 main.css를 부르는 까닭이 여기에 있습니다. 같은 규칙이라면 우리 디자인이 reset 위에 얹혀야 하니까요.
인라인 스타일은 왜 외부 CSS로 안 덮이나
<button style="color: blue;">처럼 HTML 안에 바로 적은 스타일은 명시도가 1, 0, 0, 0이라 클래스 99개를 합쳐도 못 이깁니다.
<button class="btn" style="color: blue;">결제</button>
.btn { color: red; }
위 버튼의 글자 색은 파랑 그대로입니다. .btn은 0, 0, 1, 0인데 인라인은 1, 0, 0, 0이라 첫째 자리에서 이미 승부가 나기 때문입니다. 이 경우 외부 CSS에서 인라인을 이기는 방법은 둘뿐입니다 — HTML에서 그 style 속성을 빼서 외부 CSS로 옮기거나, 그게 정말 불가능하면 !important를 쓰는 것. 결재 라인으로 치면 임원이 직접 찍은 도장을 사원이 외부 메모로 덮을 수는 없는 셈입니다.
!important — 결재 라인을 통째로 무시하는 비상 도장
!important가 붙은 선언은 명시도 계산을 통째로 무시하고 가장 위로 올라갑니다.
.btn { color: red !important; }
<button class="btn" style="color: blue;">결제</button>
결과는 빨강입니다. 인라인이 졌습니다. 문제는 한 번 쓰기 시작하면 다음에 또 무언가를 덮으려고 !important를 거듭 쓰게 된다는 점입니다. 그러면 같은 페이지 안에 !important가 여기저기 박혀, 결국 "누가 이기는지" 코드만 봐서는 알 수 없게 됩니다. 디버깅이 추측이 됩니다.
!important가 정당하게 필요한 자리는 보통 셋입니다 — (1) 외부 라이브러리가 박은 인라인 스타일을 어쩔 수 없이 덮을 때, (2) 절대 깨져선 안 되는 유틸리티 클래스(예: .hidden { display: none !important; }), (3) 사용자가 직접 깔아 두는 보조기기용 스타일. 그 외에는 선택자의 명시도를 한 칸만 더 올리거나 캐스케이드 순서를 다시 보는 쪽이 거의 항상 낫습니다.
상속 — 부모의 결재가 자식 부서에도 내려간다
color, font-family, font-size, line-height 같은 일부 속성은 부모에 적용하면 자식 요소들이 자동으로 물려받습니다.
body { color: #333; }
이 한 줄만 적어도 페이지 안의 거의 모든 글자 색이 짙은 회색이 됩니다. 단, 자식 요소에 직접 닿는 어떤 선택자라도 있으면 — 명시도가 아무리 낮아도 — 상속을 이깁니다. 부서장이 따로 도장 찍은 결정이 본사 공통 지침보다 우선하는 식입니다.
body { color: #333; }
a { color: royalblue; }
위 CSS에서 일반 글자는 회색이지만 <a> 링크는 파랑입니다. background, border, margin 같은 속성은 상속되지 않습니다. 필요하면 inherit 키워드로 명시적으로 받아 올 수 있는데, 이 정도 디테일은 실제로 부딪힐 때 다시 들여다보면 됩니다.
함께 따라하기 — 충돌하는 스타일 4개로 우승자 맞히기
day-15 폴더에 index.html과 style.css를 만들고, 일부러 같은 버튼에 네 개의 충돌하는 스타일을 걸어 봅니다. 코드를 다 보기 전에 "어떤 색이 이길까"를 먼저 예상해 보세요.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>우선순위와 캐스케이드</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<button id="apply" class="btn primary" style="color: blue;">결재 올리기</button>
</body>
</html>
button { color: gray; } /* 0,0,0,1 */
.btn { color: green; } /* 0,0,1,0 */
.primary { color: orange; } /* 0,0,1,0 */
#apply { color: purple; } /* 0,1,0,0 */
저장하고 브라우저로 열어 보면 글자 색은 파랑입니다. 인라인 스타일(1, 0, 0, 0)이 ID·클래스 둘·태그를 한꺼번에 이겼습니다. 첫째 자리에서 이미 승부가 났기 때문에 나머지는 비교할 필요도 없습니다.
이제 style.css 마지막 줄에 다음 한 줄을 더 붙여 봅니다.
#apply { color: tomato !important; }
이번엔 결과가 토마토색으로 바뀝니다. !important가 인라인까지 통째로 덮은 것입니다.
마지막으로 그 !important 줄을 지우고, 인라인 style="color: blue;"도 HTML에서 뺀 다음 style.css 끝에 다음 두 줄을 차례로 적어 봅니다.
.btn { color: deepskyblue; }
.btn { color: seagreen; }
이번엔 글자가 seagreen(짙은 초록)이 됩니다. 두 줄의 명시도가 0, 0, 1, 0으로 같으므로 나중에 적힌 줄이 이긴 결과입니다. 명시도가 같으면 캐스케이드가 결정한다는 규칙을 눈으로 확인하는 순간입니다.
흔한 실수 3가지
1. "안 먹힌다"고 !important를 남발해 디버깅이 불가능해진다
스타일이 적용 안 될 때 가장 빠른 처방처럼 보이지만, 가장 비싼 처방입니다.
.btn { background: red !important; }
.btn.primary { background: blue !important; }
.modal .btn { background: green !important; }
당장은 화면이 의도대로 보일지 몰라도, 일주일 뒤에 색을 또 바꾸려고 들면 어디서 누가 이기는지 알아보기가 어려워집니다. !important끼리 또 충돌하면 그때는 명시도와 캐스케이드를 결국 다시 계산해야 합니다. 결재판 위에 사장 직권 도장이 거듭 찍힌 모양처럼, 무엇이 진짜 결정인지 추측해야 하는 상태가 됩니다.
.modal .btn.primary { background: blue; }
대부분의 경우 해답은 !important가 아니라 선택자의 명시도를 한 칸 올리는 것입니다. 부모 클래스를 하나 더 묶어 주거나 부모-자식 결합으로 더 구체적으로 짚어 주면 깔끔하게 이깁니다. 그래도 안 되면 캐스케이드(파일·줄 순서)부터 다시 봅니다.
2. 인라인 스타일을 일반 선택자로 덮으려 한다
<p class="warn" style="color: gray;">중요 안내</p>
.warn { color: red; }
section .warn { color: red; }
body section .warn { color: red; }
(세 줄 모두 동작하지 않습니다.) 인라인 스타일은 명시도가 1, 0, 0, 0이라 클래스·태그를 아무리 깊게 묶어도 이기지 못합니다. 가장 깨끗한 해결은 HTML에서 그 style 속성을 빼는 것입니다. 일반적으로 인라인 스타일은 외부 라이브러리가 박아 넣거나, 동적으로 JS가 박을 때만 등장해야 합니다. 정적인 디자인은 외부 CSS로 옮기는 게 원칙입니다.
<p class="warn">중요 안내</p>
.warn { color: red; }
인라인을 절대 손댈 수 없는 상황이라면, 그때야 !important가 정당한 선택지가 됩니다.
3. 캐스케이드 순서를 모르고 위·아래를 바꿔본다
"코드 위·아래가 무슨 상관이야"라고 생각하기 쉽지만, 같은 명시도에서는 캐스케이드가 결정권을 가집니다.
<head>
<link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="reset.css">
</head>
(이 순서는 의도와 반대입니다.) reset.css는 보통 브라우저 기본 스타일을 초기화하려고 만든 파일인데, 위 순서대로면 main.css의 디자인이 먼저 적용되고 그 위에 reset이 덮습니다. 결재로 치면 사원 양식 → 팀장 결재까지 다 끝낸 안건을 다시 사원 양식으로 되돌리는 셈입니다.
<head>
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="main.css">
</head>
reset을 먼저, 그 위에 우리 디자인을 얹는 순서가 올바릅니다. 같은 원리로 한 파일 안에서도 일반 규칙을 먼저 적고, 더 특수한(예외) 규칙을 뒤에 두는 흐름이 디버깅에 가장 친절합니다.
오늘 배운 것 체크리스트
- 인라인·ID·클래스·태그의 명시도 점수를
(1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)로 매길 수 있다. - 명시도가 같으면 CSS에서 더 아래에 적힌 줄이 이긴다는 캐스케이드 규칙을 안다.
- 인라인 스타일이 외부 CSS의 어떤 일반 선택자보다도 강하다는 것을 안다.
-
!important가 왜 마지막 수단이어야 하는지 설명할 수 있다. -
color,font-family같은 일부 속성이 부모에서 자식으로 자동 상속된다는 것을 안다.
자주 묻는 질문
Q. CSS 명시도는 어떻게 계산하나요?
A. (인라인, ID, 클래스/가상클래스/속성, 태그/가상요소) 네 자리 점수로 셉니다. 같은 자리는 더하고, 비교는 왼쪽 자리부터 큰 쪽이 즉시 이깁니다. 예를 들어 .btn:hover는 0, 0, 2, 0(클래스 1 + 가상클래스 1)이고, #header a는 0, 1, 0, 1입니다. 둘이 만나면 둘째 자리가 더 큰 #header a가 이깁니다 — 자릿수가 달라서 아래 자리에 클래스를 아무리 더해도 ID 한 칸을 못 넘기 때문입니다.
Q. 인라인 스타일을 외부 CSS로 덮을 수 있나요?
A. 일반 선택자로는 안 됩니다. 인라인은 명시도 1, 0, 0, 0이라 클래스·ID로 아무리 깊게 묶어도 못 이깁니다. 정답은 두 가지입니다 — HTML에서 그 style 속성을 빼서 외부 CSS로 옮기거나, 그게 정말 불가능하면 !important를 쓰는 것. 손댈 수 있다면 첫 번째가 항상 더 깨끗합니다.
Q. !important는 절대 쓰지 말아야 하나요?
A. "절대"는 아니지만, 마지막 수단으로 둡니다. 정당한 자리는 (1) 외부 라이브러리가 강제한 인라인 스타일을 어쩔 수 없이 덮을 때, (2) .hidden { display: none !important; } 같은 유틸리티 클래스, (3) 사용자가 직접 깔아 두는 보조기기용 스타일 정도입니다. 그 외에는 선택자의 명시도를 한 칸만 더 올리거나 캐스케이드 순서를 다시 보는 쪽이 거의 항상 더 낫습니다.
다음 시간 예고
내일은 박스 모델 — margin, padding, border 그리고 box-sizing을 다룹니다. 화면 위의 모든 요소가 사실은 네 겹의 상자라는 사실, 그리고 너비 계산이 자꾸 어긋나는 원인인 box-sizing 한 줄을 정리합니다.