선택자의 우선순위를 나타내는 특이도Specificity와 부모에서 기본 스타일을 받아오는 상속inheritance 개념은 CSS를 다뤄보며 항상 마주치는 요소지만, 두 용어의 상위 개념인 Cascade가 정확히 무엇인지 좀처럼 파악할 기회가 잘 없었다.

마침 최근 읽고 있는 CSS In Depth라는 책에 해당 내용이 첫 챕터에 소개되어 있었다. 이 포스트에서 다루는 내용은 CSS In Depth 1장 Cascade, specificity, and inheritance으로 무료 열람할 수 있다.

왜 이름이 ‘Cascading’ Style Sheet 인가?

CSS의 첫 글자 ‘C’는 Cascading 를 나타내는데 사전에 나온 정의를 찾아보니 대부분 공통으로 폭포수와 계단식 구조를 의미했다.

cas·cade

  1. 명사 작은 폭포
  2. 폭포처럼 쏟아지는 물
  3. 폭포처럼 흐르다
  4. 격식 풍성하게 늘어지다[매달리다]

우리말 사전에는 위의 의미를 가진 단어 정의를 찾을 수 없었다. 대신에 우리말샘 출처로 동일한 뜻을 가진 한자어를 겨우 찾을 수 있었다.

종속 縱續

3단으로 되어 있는 작은 폭포로 순수한 물이 흐르면서 바닥에서 질소가 분출되도록 만들어진, 웨이퍼를 세척하는 장치.

국어로 읽으면 더 보편적으로 쓰이는 동음의이어인 從屬과 혼동이 되어 비교적 예문이 풍부한 원어를 쓰는게 편리하지 않을까 싶다.

Cascade 는 스타일 시트가 적용되는 규칙의 집합으로, 스타일 시트의 충돌 해결법과 근본적으로 CSS가 어떻게 동작하는지 정의되어 있다고 한다.

CSS를 한번 즈음 다뤄봤으면 익히 알고 있는 아래의 스타일 적용 순위가 마치 폭포수와 같은 형태를 갖고 있어서 Cascade 라는 단어가 언어 이름에 포함한 것이다.

  1. 스타일 시트의 출처 — 사용자가 추가한 스타일은 브라우저의 기본 스타일보다 우선순위를 갖는다.
  2. 인라인 스타일
  3. 선택자의 특이도Specificity — 어느 선택자 더 우선순위를 갖는가?
  4. 소스 순서 — 스타일 시트가 선언된 순서
캐스케이드의 우선 순위를 나타내는 플로우차트 출처

캐스케이드의 우선 순위를 나타내는 플로우차트 출처

나는 여태까지 Cascading 이 폭포수라는 의미가 담겨 있어서 부모 스타일이 자식 스타일로 계승되는 패턴을 의미하는거라고 오해했다. 아주 틀린 말은 아니지만 이런 패턴은 상속inheritance이라는 cascade 의 하위 규칙이다.

특이도(Specificity) 이해하기

특이도는 브라우저가 어느 CSS 속성값이 요소에 가장 관계가 있는지 결정하고, 적용하는 하나의 척도라고 한다. 다양한 종류의 CSS 선택자로 구성된 매칭 규칙을 기반으로 한다.

매칭 규칙은 크게 4가지로 우선순위가 나뉘는데, 아래로 갈수록 명시도가 높아진다.

  1. 타입 선택자 : 태그와 가상 요소 (예: ::before)
  2. 클래스 선택자 : .example, 어트리뷰트 선택자 (예: [type="radio"]), 가상 클래스 (예 :hover).
  3. ID 선택자 (예: #example)

전역 선택자 (*), 조합자 (+, >, ~ 등) and 부정 가상 클래스 (:not()) 는 명시도에 영향을 끼치지 않는다. 다만 :not() 안에 있는 선택자는 명시도에 영향을 끼친다.

!important를 쓰지 않고 명시도를 올리기

특정한 속성값을 우선시하기 위해 !important 를 사용하면, 가장 높은 명시도가 주어져서 스타일 시트 내 자연스러운 종속을 깨트린다. 나쁜 습관으로 잘 알고 있지만 나는 더 안전한 방법을 몰라 다른 대안 없이 !important 를 쓰고 있었다.

모범사례 : 선택자를 더 사용하여 명시도를 올리기

<a class="child" href="/home">go to home</a>
a {
  background-color: green;
}

.child {
  background-color: orange;
}

간단한 예시를 통해 명시도를 올려보자. a.child 중에 클래스를 사용한 선택자의 명시도가 더 높아 링크의 배경이 오렌지색으로 결정된다.

.child {
  background-color: green;
}

.child {
  background-color: orange;
}

CSS에서 명시도가 같은 경우 소스 순서가 마지막인 스타일 시트를 선택한다. 그래서 배경을 초록색으로 바꾸려고 a 태그 선택자를 가진 스타일시트를 .child 선택자로 바꾸어도 배경색은 바뀌지 않는다. a 태그를 갖고 있는 요소에 id 애트리뷰트를 추가하지 않으면서 초록색 배경으로 바꾸려면 어떻게 해야 될까?

명시도는 선택자 종류에만 의존하지 않고 선택자의 수에 따라서도 결정된다. 이 말을 달리 하면 선택자 수를 더 추가하면 명시도가 올라간다는 사실이다.

선택자에 태그를 추가시켜 링크의 배경을 초록색으로 바꾸었다.

a.child {
  background-color: green;
}

.child {
  background-color: orange;
}

css-in-js 의 경우에는?

css-in-js 는 모던 웹 프론트엔드 개발의 CSS 작성법 중 하나이다. 대개 직접 스타일을 컴포넌트에 주입하는 사용법을 갖고 있다. 나는 이 작성법으로 어떻게 더 선택자를 추가하여 명시도를 올릴 수 있는지 고민이 되었다.

import { css } from '@emotion/core';

const buttonStyle = css`
  background-color: blue;
`

<button className={buttonStyle}>Press the button</button>
<button class="css-1jc3q86">Press the button</button>

일반적인 css-in-js 라이브러리는 빌드 결과로 문자열 타입의 클래스 네임을 만들어낸다. 명시도는 같은 클래스 이름을 중복해서 사용해도 올라가는데 따라서 이를 그대로 아래 예시처럼 응용할 수 있다.

import { css } from '@emotion/core';
import cn from 'classnames'

const buttonStyle = css`
  background-color: blue;
`

<button className={cn(buttonStyle, buttonStyle)}>Press the button</button>
<button class="css-1jc3q86 css-1jc3q86">Press the button</button>

더 간편한 방법으로 && 선택자를 사용하면 스타일 시트 정의에 선택자가 하나 더 붙는다. SASS 에서는 && 는 오류가 나오고 &#{&} 를 사용해야 클래스가 하나 더 붙는다.

const buttonStyle = css`
  && {
    background-color: blue;
  }
`
.css-1jc3q86.css-1jc3q86 {
  padding: 0;
}

은탄환은 없다

These two rules can be good advice, but don’t cling to them forever. There are exceptions where they can be okay, but never use them in a knee-jerk reaction to win a specificity battle. two rules of thumb

저자는 마지막에 이런 규칙을 무조건 따르지 말라고 한다.1 상황에 따라 적절한 해결책이 여기서 소개한 규칙과 맞지 않을 수 있기 때문이다.

다음으로 내가 읽어볼 챕터로는 Working with relative units로 relative units 를 소개하는 내용이다. 다음 챕터의 내용 중에 “The end of the pixel-perfect web"라는 강렬한 소제목이 있어 흥미를 갖고 있다.

현재 진행 중인 프로젝트에서 relative units 사용을 고민해보았지만, 픽셀과 달리 특별한 이점을 몰라 보류하고 있는데, 여기서 뭔가 배울 수 있었으면 좋겠다.


  1. 이 글에서 소개한 !important 사용을 지양하는 규칙 외에 선택자에 id 사용을 지양하자는 규칙이 책에 있다. 글의 구성 상 id에 대한 내용은 없어도 괜찮다고 생각해서 이번 글에서 생략했다. ↩︎