HTML로 문서의 뼈대를 세우는 법을 익히고 나서, 나는 곧바로 CSS라는 벽에 부딪혔다. 분명히 색을 바꾸는 규칙을 적었는데 화면은 꿈쩍도 안 하고, 어떤 규칙은 지우지도 않았는데 갑자기 다른 규칙을 이겼다. 처음엔 브라우저가 변덕을 부린다고 생각했지만, 알고 보니 전부 우선순위라는 명확한 규칙 안에서 벌어지는 일이었다.


이번 편은 CSS가 어떤 원리로 요소에 적용되는지, 그리고 여러 규칙이 충돌할 때 누가 이기는지를 다룬다. 선택자로 대상을 고르는 법부터 우선순위 계산, 캐스케이드의 순서까지 내가 헤맸던 지점을 따라가며 정리한다. 이 원리를 손에 쥐면 스타일이 안 먹는 대부분의 상황을 스스로 진단할 수 있다.

1. CSS는 대상을 고르고 값을 입힌다

CSS의 기본 문장은 단순하다. 어떤 요소를 고를지 정하는 선택자가 있고, 그 뒤 중괄호 안에 속성과 값의 쌍을 나열한다. 예를 들어 문단의 글자색을 정하려면 p를 선택자로 두고 색 속성에 원하는 값을 적는다. 이 고른다와 입힌다의 두 단계가 CSS의 전부라고 해도 지나치지 않다.


내가 처음 오해한 건 스타일을 요소마다 일일이 붙여야 한다고 생각한 것이었다. 그래서 태그 안에 인라인으로 스타일을 잔뜩 적었고, 같은 모양을 반복할 때마다 복사해 붙였다. 규모가 조금만 커져도 이 방식은 손을 댈 수 없게 엉켰다. 선택자로 한 번에 여러 요소를 겨냥하는 힘을 그때는 몰랐다.


선택자는 스타일을 특정 요소에서 떼어내 규칙으로 만든다. 클래스 하나를 정의해두면 그 클래스를 붙인 모든 요소가 같은 모양을 공유한다. 디자인을 바꿔야 할 때 규칙 한 곳만 고치면 전부 따라 바뀐다. 내용과 표현을 분리한다는 말이 바로 이 뜻이었고, 이 분리가 유지보수의 핵심이었다.


스타일을 문서에 연결하는 방법은 세 가지다. 태그 안에 직접 적는 인라인, 문서 머리에 스타일 블록을 두는 내부 방식, 그리고 별도 파일을 연결하는 외부 방식이다. 나는 규모가 있는 작업이라면 무조건 외부 파일을 쓴다. 여러 페이지가 하나의 스타일을 공유하고, 브라우저가 그 파일을 한 번 받아 캐시에 쟁여두기 때문이다.


연결 방식마다 힘의 세기가 다르다는 점도 중요하다. 인라인 스타일은 선택자로 적은 규칙보다 강하게 작용해서, 나중에 외부 규칙으로 덮으려 해도 잘 먹지 않는다. 그래서 나는 급하게 인라인으로 때운 스타일이 나중에 발목을 잡는 경험을 여러 번 했다. 어디에 어떻게 적느냐가 이미 우선순위의 시작이었다.


결국 CSS를 잘 쓴다는 건 선택자를 잘 설계한다는 말과 거의 같다. 대상을 정확히 겨냥하되 너무 좁지도 넓지도 않게 잡는 감각이 필요하다. 이 감각은 다음에 다룰 우선순위 개념을 이해해야 비로소 잡힌다. 선택자가 강할수록 겨냥은 정확해지지만, 나중에 덮어쓰기가 어려워지는 대가가 따른다.

2. 선택자의 종류와 조합

가장 기본이 되는 선택자는 요소의 종류를 그대로 고르는 타입 선택자다. 문단이면 p, 제목이면 h2처럼 태그 이름을 그대로 쓴다. 여기에 클래스 선택자는 점을, 아이디 선택자는 우물 정 기호를 앞에 붙여 표현한다. 전체를 겨냥하는 별표 선택자도 있지만 무겁게 걸리므로 나는 최소한으로만 쓴다.


여러 대상을 한 규칙으로 묶고 싶으면 쉼표로 나열한다. 제목과 문단에 같은 여백을 주고 싶을 때 두 선택자를 쉼표로 이어 한 번에 적는 식이다. 반대로 관계를 좁히고 싶을 때는 조합자를 쓴다. 조합자는 요소들 사이의 위치 관계를 근거로 대상을 추려낸다.


조합자는 네 가지가 핵심이다. 공백은 자손 전체를, 홑화살괄호는 바로 아래 자식만, 더하기는 바로 뒤 형제 하나를, 물결표는 이후 모든 형제를 고른다. 나는 이 네 가지의 차이를 몸으로 익히기 전까지 스타일이 엉뚱한 곳에 번지는 일을 자주 겪었다. 특히 자손과 자식의 차이를 뭉뚱그리면 깊은 곳까지 스타일이 새어 나간다.


속성 선택자도 실무에서 아주 유용하다. 링크의 목적지가 특정 문자열로 시작하는지, 파일 확장자로 끝나는지, 특정 값을 포함하는지에 따라 스타일을 다르게 줄 수 있다. 외부 링크나 내려받기 링크에 표시를 자동으로 붙일 때 나는 이 속성 선택자를 즐겨 쓴다. 마크업을 건드리지 않고도 조건에 맞는 요소만 골라낸다.


비교적 최근 표준으로 자리 잡은 관계형 선택자는 판을 바꿨다. 예전에는 부모나 앞선 형제를 선택할 방법이 없어 자바스크립트에 의존했는데, 이제는 특정 자식을 가진 부모를 직접 겨냥할 수 있다. 예컨대 이미지를 품은 카드만 골라 여백을 없애는 식이다. 다만 이 선택자는 서브트리를 훑는 비용이 있어 조건을 좁게 잡아야 한다.


선택자를 조합할수록 표현력은 커지지만 우선순위도 함께 올라간다. 나는 처음에 정확히 겨냥하겠다며 선택자를 길게 늘어놓았다가, 나중에 그 규칙을 덮어쓰지 못해 애를 먹었다. 겨냥의 정확함과 나중의 유연함은 서로 반대 방향으로 당긴다. 이 긴장을 다스리는 것이 다음에 볼 우선순위 계산의 핵심이다.

3. 우선순위 계산의 원리

여러 규칙이 같은 요소의 같은 속성을 두고 다툴 때, 브라우저는 우선순위를 계산해 승자를 정한다. 이 계산은 네 자리의 점수로 이루어진다고 생각하면 편하다. 인라인 스타일이 가장 앞자리, 그다음이 아이디, 그다음이 클래스와 속성과 상태, 마지막이 타입과 가상 요소다.


비교는 앞자리부터 이루어진다. 아이디를 하나 쓴 규칙은 클래스를 아무리 많이 쓴 규칙보다 무조건 강하다. 클래스 열 개를 나열해도 아이디 하나를 이길 수 없다는 뜻이다. 나는 이 원리를 모른 채 클래스를 자꾸 덧붙이며 왜 안 먹히나 답답해했는데, 상대가 아이디였다면 애초에 이길 수 없는 싸움이었다.


같은 자리끼리는 개수로 비교한다. 클래스를 두 개 쓴 규칙은 하나 쓴 규칙을 이긴다. 그래서 정확히 겨냥하려고 선택자를 길게 쓰면 점수가 올라가 강해지지만, 그만큼 나중에 그 규칙을 덮으려면 더 강한 선택자를 동원해야 한다. 이것이 선택자를 무겁게 쓰면 유지보수가 어려워지는 이유다.


점수가 완전히 같으면 그때는 순서가 승부를 가른다. 나중에 선언된 규칙이 이긴다. 그래서 스타일 파일에서 규칙을 적는 순서, 그리고 여러 파일을 연결하는 순서가 결과에 영향을 준다. 나는 예전에 규칙 두 개가 똑같아 보이는데 하나만 먹히는 걸 두고 한참 고민했는데, 답은 단순히 아래 규칙이 이긴 것이었다.


가상 클래스 중에는 우선순위를 다루는 특별한 것들이 있다. 여러 선택자를 묶는 함수형 선택자는 인자 중 가장 강한 것의 점수를 따르지만, 우선순위를 0으로 만들어주는 선택자도 있다. 나는 남이 쉽게 덮어쓸 수 있어야 하는 기본 스타일을 정의할 때 이 0짜리 선택자를 쓴다. 힘을 일부러 빼두는 셈이다.


이 모든 계산 위에 예외처럼 군림하는 것이 강제 표시다. 값 뒤에 이 표식을 붙이면 우선순위 계산을 무시하고 이겨버린다. 편해 보이지만 이건 함정이다. 한 번 쓰기 시작하면 그걸 덮으려고 또 붙이게 되고, 결국 문서 전체가 강제 표식 전쟁터가 된다. 나는 이걸 최후의 수단으로만 남겨둔다.

4. 캐스케이드와 상속

CSS라는 이름 자체에 폭포라는 뜻의 캐스케이드가 들어 있다. 여러 출처에서 온 규칙들이 폭포처럼 흘러내리며 겹겹이 쌓이고, 그 위에서 우선순위가 최종 값을 결정한다. 규칙의 출처에는 브라우저 기본 스타일, 사용자 설정, 그리고 내가 작성한 스타일이 있다. 보통 내 스타일이 가장 세게 적용된다.


캐스케이드를 이해하면 브라우저 기본 스타일이 왜 존재하는지 보인다. 아무 스타일도 주지 않아도 제목이 굵고 크게, 링크가 파랗게 나오는 건 브라우저가 미리 깔아둔 기본 규칙 덕분이다. 내 규칙은 이 기본 위에 얹혀 필요한 부분만 덮어쓴다. 그래서 초기화 스타일로 이 기본값을 먼저 정리하는 작업이 흔하다.


상속은 캐스케이드와 짝을 이루는 개념이다. 부모에 준 속성 일부가 자식에게 자동으로 물려 내려간다. 글자색이나 글꼴, 줄 높이 같은 텍스트 관련 속성이 대표적이다. 반대로 여백이나 테두리처럼 상자에 관한 속성은 상속되지 않는다. 이 구분을 알면 왜 어떤 값은 한 번만 줘도 퍼지고 어떤 값은 안 퍼지는지 이해된다.


나는 본문 전체의 글꼴과 줄 높이를 최상위 요소에 한 번만 지정한다. 상속 덕에 그 아래 모든 텍스트가 같은 기준을 따르기 때문이다. 개별 요소마다 글꼴을 다시 적는 건 낭비일 뿐 아니라 나중에 통째로 바꾸기도 어렵게 만든다. 상속을 잘 쓰면 규칙의 양이 눈에 띄게 줄어든다.


상속을 세밀하게 제어하는 키워드도 있다. 값을 부모 그대로 물려받게 하는 것, 기본값으로 되돌리는 것, 상속 여부에 따라 초기화하는 것 등이다. 나는 컴포넌트의 경계에서 바깥 스타일이 새어 들어오는 걸 막을 때 이런 키워드로 값을 리셋한다. 상속을 끊어야 독립적인 조각을 만들 수 있는 경우가 있다.


결국 캐스케이드와 상속은 CSS를 규모 있게 다루는 두 축이다. 캐스케이드는 규칙들이 어떻게 겹쳐 최종값을 만드는지를, 상속은 값이 트리를 따라 어떻게 흘러내리는지를 설명한다. 이 둘을 머릿속에 그리면 스타일이 예상과 다르게 나올 때 어디를 살펴야 할지 감이 잡힌다. 대부분의 왜 안 먹지는 이 두 개념 안에서 풀린다.

5. 우선순위 전쟁을 피하는 습관

내가 오래 시행착오 끝에 얻은 첫 번째 습관은 우선순위를 낮고 평평하게 유지하는 것이다. 되도록 클래스 위주로 겨냥하고, 아이디를 스타일 훅으로 쓰지 않는다. 아이디는 점수가 너무 높아 한번 쓰면 그 위를 덮기가 힘들다. 대부분의 스타일이 비슷한 세기를 가질 때 순서만으로 예측 가능하게 관리된다.


두 번째는 선택자를 필요 이상으로 길게 쓰지 않는 것이다. 정확히 겨냥하겠다고 조상 선택자를 줄줄이 붙이면 그 순간엔 잘 맞지만, 나중에 재사용과 덮어쓰기가 모두 어려워진다. 나는 되도록 한두 단계 안에서 대상을 겨냥하려 애쓰고, 겨냥이 어렵다면 마크업에 클래스를 하나 더 붙이는 쪽을 택한다.


세 번째는 강제 표식을 봉인하는 것이다. 급할 때 이 표식은 달콤하지만, 그 대가는 반드시 나중에 청구된다. 정말 어쩔 수 없는 예외가 아니라면 나는 이걸 쓰지 않고, 대신 왜 원래 규칙이 안 먹는지를 먼저 파헤친다. 원인은 대개 선택자 점수 차이나 순서 문제이고, 그걸 바로잡으면 표식 없이 해결된다.


네 번째는 캐스케이드 층을 활용하는 것이다. 비교적 최근 표준으로 규칙 묶음에 명시적인 층위를 부여할 수 있게 되었다. 초기화, 기본, 컴포넌트, 유틸리티 같은 층을 순서대로 선언해두면 점수 싸움 없이 층의 순서로 우선순위가 정해진다. 이 방식은 강제 표식에 의존하던 낡은 습관을 상당 부분 대체한다.


다섯 번째는 힘을 일부러 빼는 선택자를 아는 것이다. 우선순위 0을 만드는 선택자로 기본 스타일을 정의하면, 사용자가 별다른 고민 없이 그 위를 덮어쓸 수 있다. 나는 공용 컴포넌트의 기본값을 이렇게 약하게 깔아두고, 페이지마다 필요한 변형을 가볍게 얹는다. 강함이 아니라 약함이 유연함을 준다는 걸 이 선택자에서 배웠다.


정리하면 CSS는 선택자로 대상을 고르고 값을 입히는 언어이며, 규칙이 충돌하면 우선순위와 순서가 승자를 정하고, 값은 캐스케이드와 상속을 타고 흐른다. 이 원리를 쥐고 있으면 스타일이 안 먹는 상황의 팔 할을 스스로 진단할 수 있다. 다음 편에서는 모든 요소가 화면에서 차지하는 공간의 기본, 박스 모델을 파고든다.