Skip to content
This page has been auto-translated and may contain errors.View in English

CSS는 어떻게 동작하는가

docs.scrimba.com

일반 HTML 페이지는 이미 브라우저 자체의 기본 스타일이 적용된 채로 나타납니다. 파란색 밑줄이 있는 링크, 세리프 없는 검은색 텍스트, 크기가 단계적으로 줄어드는 제목들처럼 말이죠. CSS는 이런 기본 구조를 가져와서 원하는 모양으로 바꾸는 도구입니다. 하지만 색상 하나를 바꾸거나 박스 하나를 옮기기 전에, 먼저 스타일이 애초에 어떻게 페이지에 도달하는지, 그리고 두 규칙이 같은 요소를 두고 충돌할 때 무슨 일이 벌어지는지 알아야 합니다.

페이지에 CSS 연결하기

페이지에 CSS를 추가하는 방법은 세 가지이며, 그 차이는 대부분 스타일을 어디에 작성하느냐에 있습니다.

첫 번째는 **인라인 스타일(inline style)**로, style 속성을 이용해 요소에 직접 작성합니다.

html
<p style="color: teal;">A single teal paragraph.</p>

이 방식은 딱 그 문단 하나에만 스타일이 적용됩니다. 두 번째 방법은 페이지의 <head> 안에 내부 <style> 블록을 넣는 것으로, 이 방법을 쓰면 여러 요소를 한꺼번에 스타일링할 수 있습니다. 세 번째, 그리고 여러분이 실제로 선택해야 할 방법은 페이지에 별도의 CSS 파일을 연결하는 것입니다. 이 파일에 스타일을 한 번만 작성해두면, 이 파일을 연결한 모든 페이지가 그 스타일을 그대로 사용하게 됩니다. 옷장을 떠올려 보세요. 사람마다 일일이 옷차림을 설명하는 대신, 드레스 코드를 한 번 정해두면 모두가 그것을 따르는 것과 같습니다.

CSS는 세 가지 방식으로 페이지에 연결되지만, 이 셋은 동등하지 않습니다. 인라인 스타일은 요소 자체에 style 속성으로 존재하며 오직 그 요소에만 영향을 줍니다. **내부 스타일시트(internal stylesheet)**는 <head> 안의 <style> 블록으로, 페이지 전체에 걸쳐 요소를 대상으로 할 수 있습니다. 외부 스타일시트는 <head>에서 연결하는 .css 파일입니다.

html
<link rel="stylesheet" href="styles.css">

기본값으로는 외부 스타일시트를 선택하세요. 이렇게 하면 스타일링이 마크업 곳곳에 흩어지지 않고 한 곳에 모이므로, 구조는 HTML에 남고 표현은 CSS에 남습니다. 이는 CSS란 무엇인가에서 설명한 것과 같은 분리 원칙입니다. 브라우저는 이 파일을 캐시하므로, 페이지를 오가는 방문자는 스타일을 한 번만 내려받아 재사용하게 되고, 버튼 스타일 같은 규칙도 모든 버튼에 일일이 복사하는 대신 한 번만 작성하면 됩니다.

CSS를 연결하는 방식은 세 가지이며, 각각은 이 장의 뒷부분에서 만나게 될 캐스케이드의 특정 위치에 대응됩니다. 인라인 스타일(요소에 붙는 style 속성)은 오직 그 요소에만 적용되며 충돌 시 매우 높은 가중치를 갖는데, 바로 이 점 때문에 인라인 스타일에 의존하면 문제가 생깁니다. 스타일시트로부터 깔끔하게 재정의하기가 어렵기 때문입니다. 내부 <style> 블록은 하나의 문서에만 범위가 한정됩니다. <link rel="stylesheet">로 연결하는 외부 스타일시트는 단순히 정돈된 코드를 위해서가 아니라 그보다 더 중요한 이유로 실무 기본값이 됩니다.

외부 파일은 한 번에 세 가지 이점을 제공합니다. 관심사의 분리(마크업과 스타일링이 독립적으로 변경됨), HTTP 캐싱(재방문자는 각 HTML 응답에 스타일을 다시 내려받는 대신 이미 다운로드한 파일을 모든 페이지에서 재사용함), 그리고 재사용성(한 번 작성한 규칙이 사이트 전체에 쓰임)입니다. 이에 대한 대가는 별도의 요청이 발생한다는 점이며, 그래서 첫 페인트를 위한 아주 작은 필수 스타일은 인라인으로 넣기도 하지만, 일반 원칙은 여전히 유효합니다. 외부 스타일시트에 작성하고, 인라인 스타일은 습관이 아니라 특정 목적을 위한 예외로만 다루세요.

외부 방식을 전체 예시로 보면, 스타일시트를 연결하는 HTML 파일과 그 스타일시트 자체는 다음과 같습니다.

html
<!-- index.html -->
<head>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <p class="intro">Welcome to the site.</p>
</body>
css
/* styles.css */
.intro {
  color: teal;
  font-size: 1.25rem;
}
Juno페이지에 CSS 연결하기 CSS를 추가하는 방법은 세 가지입니다. 요소에 style로 직접 넣거나, <style> 블록에 넣거나, <link>로 연결하는 별도 파일에 넣는 방법입니다. 별도 파일을 사용하세요. 스타일을 한 번만 작성하면 그 파일을 연결한 모든 페이지가 똑같은 모습이 됩니다.
Juno페이지에 CSS 연결하기 인라인 스타일은 요소 하나에만 적용되고, <style> 블록은 페이지 하나를 다루고, 외부 .css 파일은 사이트 전체를 다룹니다. 기본값은 외부 파일로 하세요. 표현을 마크업에서 분리해 두고, 브라우저가 이를 캐시하며, 하나의 규칙이 일치하는 모든 요소에 쓰입니다. 인라인 스타일은 예외일 뿐, 일상적인 방식이 아닙니다.
Juno페이지에 CSS 연결하기 외부 스타일시트에 작성하면 분리, 캐싱, 재사용을 한 번에 얻습니다. 인라인 스타일은 캐스케이드에서 무거운 가중치를 가지므로 나중에 재정의하기 어려워지니 의도적인 예외로만 남겨두세요. 인라인으로 넣을 만한 유일한 경우는 첫 페인트를 위한 소량의 필수 스타일이며, 그럴 때조차 CSS의 대부분은 여전히 연결된 파일에 있어야 합니다.

규칙의 구조

CSS 코드 한 조각은 **규칙(rule)**이라는 형태로 작성되며, 모든 규칙은 같은 모양을 가집니다. 카드를 스타일링하는 규칙을 하나 보겠습니다.

css
.card {
  color: navy;
  padding: 16px;
}

맨 앞의 .card는 **선택자(selector)**로, 이 규칙이 어떤 요소에 적용될지를 정합니다. 이 경우에는 card 클래스를 가진 모든 요소가 대상입니다. 중괄호 안에 있는 모든 내용은 선언 블록입니다. color: navy;처럼 그 안에 있는 각 줄은 하나의 선언으로, 속성(color)과 그 속성에 부여할 값(navy)이 콜론으로 이어지고 세미콜론으로 끝납니다. 규칙 전체를 문장처럼 읽어 보면 이렇습니다. "모든 카드에 대해, 색상을 navy로 하고 패딩을 16픽셀로 하라."

모든 규칙은 같은 요소들로 이루어져 있으며, 이 요소들에 이름을 붙여두면 CSS의 나머지 부분을 이야기하기가 훨씬 쉬워집니다.

css
.card {
  color: navy;
  padding: 16px;
}

선택자(selector)(.card)는 규칙이 대상으로 삼는 요소를 고릅니다. 중괄호는 선언 블록을 담습니다. 그 안의 각 property: value; 쌍이 선언이며, color가 속성, navy가 값이고, 콜론이 이 둘을 연결하고 세미콜론이 끝을 표시합니다. 세미콜론은 선언들 사이의 구분자이므로 사실 마지막 선언에는 필요하지 않지만, 그래도 붙여두는 것이 좋습니다. 나중에 선언을 하나 더 추가할 때 두 줄이 아니라 한 줄만 수정하면 되기 때문입니다. 어떤 요소가 선택자와 일치하는지, 그리고 그 선택자가 얼마나 구체적인지는 선택자 장에서 다룹니다.

이 문법은 간단하지만 정확히 짚어둘 가치가 있습니다. 이후에 나오는 모든 개념이 바로 이 용어들로 정의되기 때문입니다. 규칙은 선택자(규칙이 어떤 요소에 적용될지를 결정하는 패턴)와 그 뒤에 이어지는 선언 블록(중괄호)으로 이루어집니다. 블록 안에는 선언들의 목록이 있고, 각 선언은 콜론으로 짝지어진 속성과 값이며 세미콜론으로 끝납니다.

css
.card {
  color: navy;
  padding: 16px;
}

이 정확성은 디버깅에서 실제로 도움이 됩니다. 예를 들어 속성 이름을 잘못 썼거나 유효하지 않은 값을 넣은 잘못된 선언은 파서가 그 선언만 따로 버리며, 블록의 나머지는 그대로 적용됩니다. CSS는 규칙 단위가 아니라 선언 단위로 실패하기 때문에, 오타 하나가 카드 전체를 망가뜨리는 대신 그 한 줄만 조용히 사라지는 것입니다. 세미콜론은 종결자가 아니라 구분자이므로 마지막 선언 뒤에는 없어도 되지만, 붙여두면 나중에 줄을 하나 추가하면서 세미콜론을 빼먹어 규칙이 깨지는 일을 막아줍니다. 다음에 캐스케이드를 배울 때, "어떤 선언이 이기는가"는 항상 속성별로 이 선언 수준에서 결정된다는 점을 기억하세요.

Juno규칙의 구조 규칙은 선택자와 중괄호 안의 선언들로 이루어집니다. .card 같은 선택자가 요소를 고르고, color: navy; 같은 각 선언은 속성과 값이 콜론으로 이어지고 끝에 세미콜론이 붙습니다. 이 구성 요소들의 이름을 알고 나면 앞으로 읽는 모든 규칙이 같은 모양으로 보일 것입니다.
Juno규칙의 구조 선택자, 선언 블록, 그리고 그 안의 property: value; 선언들, 이것이 모든 규칙입니다. 세미콜론은 선언들을 구분하므로 마지막 선언에도 붙여두면 나중에 줄을 추가할 때 한 줄만 수정하면 됩니다. 선택자가 어떻게 일치하고 얼마나 구체적인지는 다음 선택자 장에서 이어집니다.
Juno규칙의 구조 규칙은 선택자와 property: value; 선언들의 블록이며, CSS는 선언 단위로 실패합니다. 유효하지 않은 한 줄은 버려지고 블록의 나머지는 살아남습니다. 그래서 우연한 오타가 카드를 망가뜨리는 대신 스타일 하나만 조용히 사라지게 하는 것입니다. 마지막에 세미콜론을 붙여두면 선언을 추가할 때 문제가 생기지 않습니다.

캐스케이드

때로는 두 규칙이 같은 요소를 두고 스타일을 겨루는데, CSS는 이 중 어느 쪽이 이길지 결정할 방법이 필요합니다. 이 결정 과정을 **캐스케이드(cascade)**라고 부르며, CSS의 C가 바로 여기서 나옵니다.

css
.card {
  color: navy;
}

.card {
  color: teal;
}

두 규칙 모두 카드를 대상으로 하고 둘 다 색상을 지정합니다. 카드는 결국 teal이 되는데, 두 규칙의 강도가 같을 때는 나중에 작성된 규칙이 이기기 때문입니다. 순서는 중요하며, 물이 흐르듯 파일 아래로 흘러내려간다는 점에서 캐스케이드라는 이름이 붙었습니다. 여기에는 두 번째 판정 기준도 있습니다. 더 구체적인 선택자가 더 일반적인 선택자를 이기므로, 카드를 정확히 겨냥한 규칙은 더 나중에 나온 규칙이라도 더 넓은 규칙을 이길 수 있습니다. 이 구체성에 대해서는 선택자 장에서 자세히 다룹니다.

여러 규칙이 같은 요소의 같은 속성을 지정할 때, **캐스케이드(cascade)**는 승자를 정하는 과정입니다. 여기서는 두 가지 요소가 결정을 내립니다. 구체성(specificity)과 소스 순서(source order)입니다.

css
.card {
  color: navy;
}

.card {
  color: teal;
}

이 두 규칙은 구체성이 똑같으므로 소스 순서가 승부를 가르고, 나중에 나온 규칙이 이깁니다. 카드는 teal이 됩니다. 하지만 구체성이 다르면 순서보다 구체성이 우선합니다. 더 구체적인 선택자는 파일에서 더 나중에 나온 일반적인 규칙이라도 이깁니다. 더 구체적인 쪽이 이기고, 구체성이 같을 때는 나중에 나온 쪽이 이긴다. 그래서 파일 뒤쪽에 재정의 규칙을 계속 쌓아 올리는 방식은 불안정한 습관이며, 선택자 장에서 구체성을 이해해야 캐스케이드가 추측 게임이 아니라 예측 가능한 것이 됩니다.

**캐스케이드(cascade)**는 하나의 요소, 하나의 속성을 두고 경쟁하는 선언들을 해결하는 알고리즘으로, 정해진 순서의 기준들을 차례로 적용해 마지막에 하나의 선언만 남길 때까지 진행됩니다.

css
.card {
  color: navy;
}

.card {
  color: teal;
}

가장 흔한 두 기준은 구체성(브라우저가 선택자로부터 계산하는 가중치)과 소스 순서(동일할 경우 나중 것이 이김)이며, 위 예시는 두 번째 기준으로 결정됩니다. 구체성이 같으므로 나중에 나온 teal이 이깁니다. 하지만 이 두 기준은 더 큰 순서 체계 안에 놓여 있습니다. 캐스케이드는 먼저 **출처(origin)와 중요도(importance)**로 정렬합니다. 브라우저 기본 스타일, 그다음 작성자(author) 스타일, 그다음 사용자 스타일이 있고, 그 위에 일반(normal)과 중요(important) 계층이 하나 더 얹혀 있습니다. !important가 표시된 선언은 더 상위 계층으로 뛰어오르는데, 바로 이것이 !important가 존재하는 이유이자 피해야 하는 이유입니다. !important는 옳아서 이기는 것이 아니라 일반적인 순서 체계를 벗어나서 이기는 것이므로, 이는 재정의라기보다 비상 탈출구에 가깝고, 한 번 !important가 등장하면 다음 수정도 흔히 또 다른 !important가 됩니다. 이는 여러분이 통제할 수 없는 스타일, 예를 들어 인라인 스타일이나 서드파티 위젯의 스타일을 이겨야 할 때만 쓰고, 그 외에는 구체성과 순서가 일을 하도록 두세요. 박스 모델을 만나 여러 규칙을 층층이 쌓기 시작할 때, 이 순서 체계가 결과를 예측 가능하게 유지해 줍니다.

Juno캐스케이드 캐스케이드는 두 규칙이 같은 것을 스타일링할 때 CSS가 승부를 가리는 방식입니다. 규칙의 강도가 같다면 나중에 작성된 쪽이 이기므로 순서가 중요합니다. 더 구체적인 선택자는 더 일반적인 선택자를 이길 수도 있는데, 이 부분은 선택자 장에서 다룹니다.
Juno캐스케이드 규칙들이 충돌하면 구체성이 먼저 결정하고, 소스 순서가 승부를 가릅니다. 더 구체적인 쪽이 이기고, 동등할 때는 나중 규칙이 이깁니다. 파일 아래쪽에 재정의를 쌓아 올리는 방식은 어느 순간 통하지 않게 되므로, 캐스케이드를 믿기 전에 구체성을 다루는 선택자 장을 읽어볼 가치가 있습니다.
Juno캐스케이드 캐스케이드는 먼저 출처와 중요도로 정렬하고, 그다음 구체성, 그다음 소스 순서로 정렬하며, 이 모든 것을 속성별로 해결합니다. !important는 옳아서가 아니라 계층을 뛰어넘어서 이기므로, 통제할 수 없는 스타일을 이기기 위한 비상 탈출구로만 다루고 일상적인 재정의 수단으로는 쓰지 마세요. !important 하나는 다음 것을 부르는 경향이 있습니다.

상속

박스에 색을 지정하면 그 안의 텍스트도 직접 스타일링하지 않았는데도 종종 같은 색으로 바뀝니다. 이렇게 스타일이 아래로 전달되는 것을 **상속(inheritance)**이라고 부릅니다.

css
.site-header {
  color: white;
  font-family: Georgia, serif;
}

헤더 안의 모든 텍스트, 제목이든 링크든 태그라인이든, 자기만의 규칙 없이도 그 흰색과 그 글꼴을 그대로 물려받는데, colorfont-family가 상속되는 속성이기 때문입니다. 다만 모든 것이 아래로 전달되지는 않습니다. padding 같은 여백 속성은 상속되지 않습니다. 헤더에 준 패딩은 헤더에만 남아 있고 그 안의 요소들에는 나타나지 않습니다. 대략적인 기준으로, 텍스트 관련 스타일은 자식에게 흘러 내려가고, 여백이나 테두리 같은 박스 관련 스타일은 그 자리에 머무릅니다.

일부 속성은 자신의 값을 자손 요소에 자동으로 전달하고, 일부는 그렇지 않습니다. 이 동작을 **상속(inheritance)**이라고 부르며, 어떤 속성이 상속되는지 알아두면 반복되는 규칙을 많이 줄일 수 있습니다.

css
.site-header {
  color: white;
  font-family: Georgia, serif;
}

텍스트 관련 속성은 상속됩니다. color, font-family, font-size, line-height 등은 모두 자손에게 흘러가므로, 컨테이너 하나에 한 번만 설정하면 그 안의 모든 것에 스타일이 적용됩니다. 박스 관련 속성은 그렇지 않습니다. padding, margin, border, background는 값을 설정한 요소에만 적용됩니다. 보통은 상속되지 않는 값을 상속시키고 싶을 때는 inherit 키워드를 쓰면 부모의 계산된 값을 강제로 가져올 수 있습니다. 상속과 캐스케이드는 서로 별개의 시스템이지만 함께 작동합니다. 캐스케이드는 한 요소에서 이기는 선언을 결정하고, 상속은 그 속성에 아무 규칙도 적용되지 않은 자식이 무엇을 기본값으로 삼을지를 결정합니다. 타이포그래피는 이 상속에 크게 의존하며, 타이포그래피 장에서 이 내용을 이어서 다룹니다.

**상속(inheritance)**은 어떤 요소에 캐스케이드로 결정된 값이 없는 속성이 그 요소의 부모로부터 값을 가져오는 메커니즘입니다. 각 CSS 속성은 상속되거나 상속되지 않는 것으로 정의되어 있습니다. 대부분의 텍스트 속성(color, font-family, font-size, line-height, text-align)은 상속되고, 대부분의 박스 속성(padding, margin, border, background)은 상속되지 않습니다. 여백을 모든 자손에게 상속시키면 도저히 쓸 수 없는 결과가 나오기 때문입니다.

css
.site-header {
  color: white;
  font-family: Georgia, serif;
}

기본 동작이 원하는 것과 다를 때는 네 가지 명시적인 키워드로 선언별로 이를 제어할 수 있습니다. inherit은 상속되지 않는 속성이라도 부모의 계산된 값을 그대로 가져오고, initial은 그 속성의 명세상 기본값으로 되돌리며, unset은 상속되는 속성에는 inherit처럼, 나머지에는 initial처럼 동작하고, revert는 브라우저의 기본 스타일로 되돌립니다. 큰 그림에서 보면 상속과 캐스케이드는 함께 요소의 **계산된 값(computed value)**을 만들어냅니다. 캐스케이드는 이기는 선언이 있으면 그것을 선택하고, 없으면 상속되는 속성은 부모로부터 값을 가져오고 상속되지 않는 속성은 초기값으로 떨어집니다. 속성마다, 요소마다 존재하는 이 하나의 계산된 값이 실제로 레이아웃과 페인팅이 사용하는 값이며, 커스텀 속성 장에서는 상속을 직접 활용해 디자인 토큰이 컴포넌트 트리를 따라 흐르도록 합니다.

Juno상속 상속은 텍스트 스타일이 박스 안의 요소들로 흘러 내려가는 것입니다. 헤더에 colorfont-family를 지정하면 그 안의 텍스트가 별도의 작업 없이도 그것을 그대로 물려받습니다. padding 같은 여백은 아래로 전달되지 않고 지정한 곳에 머무릅니다. 대략적인 규칙은, 텍스트 스타일은 자식에게 이동하고 박스 스타일은 그 자리에 머무른다는 것입니다.
Juno상속colorfont-family 같은 텍스트 속성은 자손에게 상속되고, padding이나 border 같은 박스 속성은 상속되지 않습니다. 컨테이너 하나에 스타일을 지정하면 그 안의 텍스트 관련 요소들이 모두 그것을 따릅니다. 보통은 상속되지 않는 값을 상속시켜야 할 때는 inherit 키워드가 부모로부터 그 값을 가져옵니다.
Juno상속 각 속성은 정의상 상속되거나 상속되지 않습니다. 텍스트 속성은 상속되고 박스 속성은 상속되지 않습니다. inherit, initial, unset, revert 키워드를 쓰면 선언별로 이를 재정의할 수 있습니다. 상속과 캐스케이드는 함께 속성마다, 요소마다 하나의 계산된 값을 결정하며, 실제로 레이아웃과 페인팅이 사용하는 것은 바로 이 값입니다.