Computer >> 컴퓨터 >  >> 프로그래밍 >> CSS

CSS 특이성(Specificity)이란 무엇일까? 선택자 우선순위 완벽 정리

CSS 특이성(Specificity)이란?

CSS에는 각 선택자의 '힘'을 수치로 계산하는 규칙이 있습니다. 여기서 말하는 힘이란 어떤 선택자가 더 강력한지, 즉 특이성(specificity)이 높은지를 의미합니다. 특이성 수치가 가장 높은 선택자는 다른 약한 선택자들이 적용한 스타일과 무관하게 항상 HTML 요소의 최종 모습을 결정합니다.

예를 들어 버튼(button) 요소가 있다면, 스타일을 적용하는 방법은 여러 가지입니다. CSS에서 가장 많이 쓰이는 세 가지 선택자는 type(타입), class(클래스), id입니다.

세 가지 기본 선택자

타입 선택자를 사용하면 요소에 직접 스타일을 지정할 수 있습니다:

button {
  background-color: red;
}

클래스 속성을 사용할 수도 있습니다:

.button {
  background-color: red;
}

아니면 id 속성을 사용할 수 있습니다:

#button {
  background-color: red;
}

각 선택자의 기본 수치는 1입니다.

그렇다면 위의 세 선택자(타입, 클래스, id)가 모두 같은 값(1)을 가질 때, 하나의 스타일시트에서 동시에 사용되면 어떤 규칙이 이길까요?

정답은 ID(#button)입니다. ID는 클래스와 타입 선택자보다 우선순위가 높기 때문입니다.

ID가 없다면 어떻게 될까요?

그 경우에는 클래스 선택자(.button)가 이깁니다. 클래스는 타입 선택자보다 우선하기 때문입니다.

CSS의 '캐스케이드(Cascade)'는 어떻게 작동할까?

'CSS = Cascading Style Sheets'라는 이름의 'Cascading(계단식)'은 대체 무슨 의미일까요?

CSS에서 모든 조건이 동등할 경우, 스타일시트에서 나중에 선언된 선택자가 승리합니다.

예를 들어 스타일시트에 같은 이름을 가진 세 개의 클래스(.hero-button)를 추가했다고 해봅시다. 세 클래스 모두 같은 속성(background-color)을 대상으로 하지만, 각각 다른 값 — 초록색, 파란색, 빨간색 — 을 가지고 있습니다.

.hero-button {
	background-color: green;
}

.hero-button {
		background-color: blue;
}

.hero-button {
	background-color: red;
}

그리고 이 세 클래스를 모두 같은 HTML 요소에 적용합니다:

<button class="hero-button">Button</button>

스타일시트에서 마지막에 선언된 .hero-button 클래스가 앞선 같은 이름의 클래스들보다 우선하므로, 버튼의 배경색은 빨간색이 됩니다. CSS 캐스케이드가 바로 이렇게 작동합니다. 특이성 순서는 위에서 아래로 흐르며, 맨 아래의 선택자가 승리합니다. 단, 이 규칙은 모든 조건이 동등할 때만 100% 성립한다는 점을 기억하세요.

중요: 같은 이름의 선택자가 여러 개 있을 때, 스타일시트 맨 아래의 선택자가 앞선 선택자가 적용한 모든 스타일을 덮어쓰는 것이 아니라, 동일하게 지정된 속성만 덮어씁니다.

헷갈리시나요? 걱정하지 마세요. 곧 이해가 될 것입니다.

속성은 '겹쳐서' 적용된다

앞서 살펴본 세 개의 .hero-button 클래스에, 이번에는 처음 두 클래스에 각각 스타일 속성을 하나 더 추가해 보겠습니다(paddingfont-size):

.hero-button {
  background-color: green;
  padding: 2rem;
}

.hero-button {
  background-color: blue;
  font-size: 2rem;
}

.hero-button {
  background-color: red;
}

이제 스타일시트에는 같은 이름을 가진 세 개의 CSS 클래스가 있고, 모두 버튼 요소의 background-color 속성을 대상으로 합니다 — 하지만 처음 두 클래스에는 추가 속성이 있습니다. 어떤 결과가 나올까요?

결과는 다음과 같습니다:

https://codepen.io/StrengthandFreedom/pen/mdyMGPL

보시다시피 버튼 요소는 세 개의 .hero-button 클래스의 스타일 속성을 모두 합쳐서(stack) 적용받습니다. 즉, 버튼은 세 가지 속성을 모두 갖게 됩니다:

  padding: 2rem;
  font-size: 2rem;
  background-color: red;

다시 말해, 스타일시트에 이름이 완전히 같은 CSS 선택자가 여러 개 있을 때는, 선택자들이 동일한 속성을 대상으로 하는 경우에만 그 속성이 덮어쓰기 됩니다.

위 예제에서 세 클래스 모두 background-color 속성을 대상으로 하며, 여기서는 CSS 캐스케이드 규칙(소스 순서, Source Order)에 따라 맨 아래 클래스가 승리합니다. 하지만 마지막 .hero-button 클래스는 padding이나 font-size를 지정하지 않았기 때문에, 처음 두 클래스의 해당 속성들은 그대로 버튼에 적용됩니다.

실무에서 마주치는 문제: 방대하고 정리 안 된 CSS 코드

이 내용을 굳이 강조하는 이유는, 어차피 여러분도 언젠가 거대하고 정리되지 않은 CSS 코드베이스를 다루게 되기 때문입니다. 여러 개발자가 함께 작업하다 보면 코드가 엉켜서 골치 아픈 상황이 생기기 마련이죠.

가장 흔한 시나리오 중 하나는 서로 다른 개발자들이 이미 스타일시트 어딘가에 존재하는 CSS 클래스를 또 추가하는 것입니다. CSS가 앞서 본 것처럼 속성들을 서로 겹쳐 적용한다는 사실을 모르거나 잊었다면 금방 혼란에 빠질 수 있습니다.

예를 들어 다음과 같은 속성을 가진 primary-button이라는 클래스를 추가했다고 해봅시다:

.primary-button {
  background-color: red
  padding: 1rem;
  font-size: 1.125rem;
}

그런데 왜인지 버튼에 본인이 추가하지 않은 그림자(shadow)가 나타납니다. 원인은 크게 두 가지일 수 있습니다. 누군가 button 요소 자체에 직접 스타일을 적용했거나:

button {
 box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1);
}

혹은 누군가 이전에 같은 이름의 클래스를 스타일시트에 추가하면서 box-shadow 속성까지 넣어둔 경우입니다:

.primary-button {
 box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1);
}

원치 않는 스타일을 제거하려면, 코드베이스에서 해당 속성이 적용된 위치를 찾아 제거하거나, 아니면 .primary-button 클래스에서 직접 덮어쓰면 됩니다:

.primary-button {
 background-color: red
 padding: 1rem;
 font-size: 1.125rem;
 box-shadow: 0; /*기존 box-shadow 값 제거*/
}

'이건 너무 지저분한 해결 방식 아닌가?'라고 생각하실 겁니다. 맞습니다. 하지만 때로는 거대하고 정리되지 않은 프로젝트(주로 레거시 코드가 많은 경우)를 다루게 되고, 기존 CSS 코드 전체에 접근할 수 없는 상황이 발생합니다(경력 있는 개발자라면 누구나 공감할 겁니다). 이런 상황에서는 기존 코드를 마음대로 수정할 수 없고 — 기존 코드를 덮어쓸(override) 수밖에 없는데, 그럴 때 CSS 특이성과 소스 순서(Source Order)가 어떻게 작동하는지 알고 있으면 큰 도움이 됩니다.

특이성 계산기 활용법과 실전 팁

온라인의 Specificity Calculator(특이성 계산기)를 사용하면 다양한 CSS 선택자의 특이성 수치를 비교할 수 있으며, 그룹 선택자(선택자 조합)도 비교해볼 수 있습니다.

팁: CSS 특이성 문제를 피하는 좋은 방법은 타입 선택자 대신 주로 클래스를 사용해 UI 스타일을 지정하고, ID는 아껴서 드물게 사용하는 것입니다. 또한 가능하면 그룹 선택자도 피하는 것이 좋습니다. 규모가 커지는 코드베이스에서 예측 불가능한 문제를 일으킬 수 있기 때문입니다.