Back Arrow이전 페이지 또는 홈 페이지로 안내하는 아이콘 링크입니다.Icon link for previous page or home page

종속형과 명시도

CSS의 기본 개념 중에 종속성명시도가 있다. 최근에서야 CSS In Depth 라는 책 스터디를 하면서 이 두 개념을 제대로 접하게 되었다. 마크업 코딩을 시작한 지 꽤 오래되었지만, 실용 위주로 CSS를 활용해서 가볍게 지나쳤지 않았나 부족함을 느껴 정리하게 되었다.

이 포스트에서 다루는 내용은 CSS In Depth 1장 Cascade, specificity, and inheritance으로 무료 열람할 수 있다.

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

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

규칙은 아래 같은 순서로 영향을 받는다.

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

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

나는 여태까지 Cascading 이 부모 스타일이 자식 스타일로 계승되는 패턴을 표현한거라고 오해했다.1 이런 패턴은 상속이라는 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 라이브러리는 렌더링 결과로 클래스 하나가 생긴다. 명시도는 같은 클래스 이름을 중복해서 사용해도 올라가는데 이를 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

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

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

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


  1. cascade 는 계단식이라는 표현도 있다. ↩︎

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