CSS 구조화와 확장


새 프로젝트의 첫 스타일시트를 작성하는 일은 즐겁습니다. 그런데 몇백 줄쯔음 지나면 뭔가 달라집니다. 버튼 하나의 색을 바꿨는데 다른 버튼 세 개의 색까지 같이 바뀌고, 제목 하나는 !important를 붙이지 않으면 위치가 꼼짝도 하지 않으며, 새 규칙을 하나 추가할 때마다 기존 규칙이 깨질 것 같은 불안감이 듭니다. CSS 문법 자체는 여전히 틀린 곳이 없습니다. 부족해진 것은 그 CSS를 지탱하는 구조이고, 스타일시트가 천 줄이든 십만 줄이든 계속 다룰 수 있는 상태로 남는지를 결정하는 것도 문법이 아니라 바로 이 구조입니다.
CSS는 왜 규모가 커지면 어려워지는가
CSS는 기본적으로 전역으로 작동합니다. 여러분이 작성하는 모든 규칙은 페이지 어디에 있든 조건에 맞는 요소라면 다 건드릴 수 있습니다. p { color: navy; }라고 쓰면 프로젝트 전체의 모든 문단이 남색으로 바뀝니다. 의도했든 아니든 상관없습니다. 작은 페이지에서는 이게 편리하지만, 프로젝트가 커지면 대부분의 문제가 여기서 시작됩니다.
이 문제는 규칙이 서로 충돌하는 형태로 나타납니다. 두 스타일시트가 모두 p를 대상으로 하거나, 일반적인 규칙과 구체적인 규칙이 같은 요소에 동시에 적용되면, 어느 쪽이 이기는지 따져봐야 합니다. CSS는 어떻게 동작하는가에서 이를 결정하는 원리를 이미 살펴봤습니다. 브라우저는 명시도와 순서로 충돌을 해결합니다. CSS를 잘 확장한다는 것은 결국 이런 충돌 자체를 만들지 않는 것에 가깝습니다.
/* 페이지의 모든 문단에 적용되는 전역 규칙 하나 */
p {
color: navy;
}
/* 다른 곳에 있는 두 번째 규칙, 같은 요소를 두고 경쟁 */
.notice p {
color: crimson; /* .notice 안에서 승리: 더 구체적이므로 */
}명시도를 낮고 평평하게 유지하기
가장 유용한 습관 하나는 **클래스**로 스타일을 입히는 것입니다. 그리고 대부분 한 번에 클래스 하나만 사용하는 것입니다. .card 같은 클래스는 빠르게 적용할 수 있고, 재사용하기 쉬우며, 나중에 필요할 때 덮어쓰기도 어렵지 않습니다. 클래스 하나는 명시도가 낮고 부담이 적기 때문입니다.
문제는 두 가지에서 옵니다. id와 길게 이어진 선택자 체인입니다. #header 같은 id는 클래스보다 덮어쓰기가 훨씬 어렵고, .sidebar ul li a 같은 체인은 특정한 구조에 고정되어 버립니다. 어디에나 붙일 수 있는 평범한 클래스를 선호하세요:
/* 권장: 단일 클래스, 낮고 평평하게 */
.nav-link {
color: navy;
}
/* 피하기: id, 나중에 덮어쓰기 어려움 */
#nav-link {
color: navy;
}
/* 피하기: 깊은 체인, 취약하고 명시도가 높음 */
.sidebar nav ul li a {
color: navy;
}.card나 .nav-link처럼 한 번에 클래스 하나만 사용합니다. 단일 클래스는 재사용하기 빠르고 나중에 덮어쓰기도 쉬워서, 딱 원하는 성질을 갖습니다. #header 같은 id와 .sidebar ul li a 같은 긴 체인은 피하세요. 둘 다 나중에 바꾸기가 훨씬 어렵습니다. 네이밍 컨벤션
클래스로 스타일을 입히기 시작하면, 다음 질문은 이름을 뭐라고 붙일지입니다. .blue나 .thing2 같은 이름은 금방 무너집니다. 그 클래스가 무엇을 위한 것인지 아무것도 말해주지 않기 때문입니다. **네이밍 컨벤션**은 이름만 보고도 클래스가 무슇을 하는지, 어디에 속하는지 알 수 있도록 클래스 이름을 짓는 합의된 방식입니다.
널리 쓰이는 것이 BEM인데, Block, Element, Modifier의 줄임말입니다. 블록은 카드 같은 컴포넌트입니다. 엘리먼트는 그 안의 부분이며 밑줄 두 개로 표기합니다. 모디파이어는 변형이며 대시 두 개로 표기합니다:
/* 블록: 컴포넌트 그 자체 */
.card { }
/* 엘리먼트: 블록의 일부, 밑줄 두 개 */
.card__title { }
.card__body { }
/* 모디파이어: 블록의 변형, 대시 두 개 */
.card--featured { }<article class="card card--featured">
<h2 class="card__title">주말 워크숍</h2>
<p class="card__body">레이아웃에 대한 짧은 소개.</p>
</article>.card 같은 블록, 밑줄 두 개로 표기하는 .card__title 같은 엘리먼트, 대시 두 개로 표기하는 .card--featured 같은 변형이 있습니다. BEM을 꼭 써야 하는 건 아니지만, 일관된 방식을 하나 정해서 계속 지키세요. 파일 구조 잡기
스타일시트가 커지면 하나의 긴 파일은 다루기 어려워집니다. 흔한 해법은 CSS를 규칙이 하는 역할별로 몇 개의 폴더로 나누는 것입니다. base 스타일(body나 제목 같은 평범한 요소의 기본값), 컴포넌트(카드, 버튼, 그 외 조각들), 유틸리티(간격이나 텍스트 정렬 클래스처럼 단일 목적의 작은 헬퍼)입니다.
파일을 나눌 때 중요한 것이 하나 있습니다. 바로 불러오는 순서입니다. 두 규칙의 명시도가 같으면 나중에 나온 규칙이 이깁니다. 그래서 나중에 불러온 스타일시트가 앞선 것을 덮어쓸 수 있습니다. 파일을 가장 일반적인 것부터 가장 구체적인 것 순으로 불러오세요:
/* main.css: 덜 구체적인 것부터 더 구체적인 것 순으로 */
@import "base/reset.css"; /* 요소 기본값 */
@import "base/typography.css";
@import "components/card.css"; /* 독립된 조각들 */
@import "components/button.css";
@import "utilities/spacing.css"; /* 마지막에 로딩되어 덮어쓸 수 있음 */
@import "utilities/text.css";캐스케이드를 명시적으로 만들기
지금까지 살펴본 모든 방법은 컨벤션을 통해 캐스케이드를 다룰 만하게 만드는 것이었습니다. 낮은 명시도, 좋은 이름, 신중한 파일 순서가 그것입니다. 더 깊이 들어가면 이 이상의 방법도 있으니, 앞으로 나아갈 방향을 알아두면 좋습니다. 최신 CSS는 어떤 파일이 우연히 어디에 있는지에 의존하지 않고 순서를 직접 제어할 수 있게 해주며, 더 많은 사람이 같은 스타일시트에서 함께 작업할 때도 캐스케이드를 예측 가능하게 유지하는 방법을 제공합니다. 프로젝트가 커지면서 이런 도구를 만나게 될 텐데, 지금까지 익힌 습관들이 바로 그것을 준비시켜주는 밑바탕입니다.

