Skip to content
This page has been auto-translated and may contain errors.View in English

CSS 선택자

docs.scrimba.com

페이지에는 온갖 요소가 가득하고 적용하고 싶은 스타일도 준비되어 있지만, 그 색이나 글꼴이 어떤 요소에 속하는지 지정하지 않으면 아무 소용이 없습니다. 선택자는 바로 그 지정을 위한 도구입니다. 이 문단, 모든 버튼, nav 안에 있는 링크만 골라내는 것처럼 말이죠. 선택자를 능숙하게 다루게 되면 CSS의 나머지 부분은 그저 무엇을 바꿀지 고르는 문제가 됩니다. 어떻게 그 요소에 도달할지는 이미 알고 있으니까요.

타입, 클래스, id 선택자

CSS 규칙은 두 부분으로 이루어집니다. 스타일을 적용할 요소를 고르는 **선택자**와, 어떻게 스타일을 적용할지 적어 놓은 선언 블록입니다. 가장 단순한 선택자는 요소의 이름 그 자체를 쓰는 것으로, 타입 선택자라고 부릅니다. p라고 쓰면 페이지의 모든 문단에 스타일이 적용됩니다.

하지만 이건 대부분 범위가 너무 넓습니다. 모든 문단이 똑같은 모습이길 바라는 경우는 드물기 때문이죠. 그래서 HTMLclass 속성을 이용해 요소에 클래스라는 이름표를 붙이고, CSS에서는 그 이름표 앞에 점을 붙여 지정합니다. 클래스를 명찰이라고 생각해 보세요. 같은 그룹으로 다루고 싶은 요소마다 같은 명찰을 달아두고, 그 명찰 하나에 스타일을 한 번만 정해주는 것입니다.

요소를 지정하는 기본적인 방법은 세 가지입니다. **타입 선택자**는 태그 이름으로 매칭합니다(p는 모든 문단과 매칭됩니다). 클래스 선택자는 class 속성으로 매칭하며 앞에 점을 붙여 씁니다(.button). id 선택자는 id 속성으로 매칭하며 앞에 해시(#)를 붙여 씁니다(#header).

스타일링의 기본 도구로는 클래스를 사용하세요. 클래스는 원하는 만큼 여러 요소에 붙일 수 있고, 하나의 요소가 여러 클래스를 가질 수도 있기 때문에, 재사용 가능한 그룹을 표현하는 데 적합합니다. .card가 붙은 모든 요소가 같은 모양을 공유하는 식입니다. 타입 선택자는 넓은 범위의 기본 스타일을 정할 때 유용하지만(예를 들어 body의 글꼴을 설정하는 경우), 그 종류의 요소 전체에 적용되기 때문에 다소 뭉뚱그린 방식입니다. id는 페이지에서 유일해야 하므로, id로 스타일을 지정하면 그 규칙이 단 하나의 요소에 고정됩니다. 그리고 뒤에서 명시도를 다룰 때 보겠지만, id는 가중치가 높아서 나중에 덮어쓰기가 까다로워집니다.

선택자의 기본은 세 가지 유형으로 정리되며, 이들의 차이는 매칭 방식뿐 아니라 가중치와도 관련이 있습니다. 타입 선택자(태그 선택자 또는 요소 선택자라고도 합니다)는 요소 이름으로 매칭합니다: p, section, input. .card처럼 쓰는 클래스 선택자는 class 속성에 해당 토큰이 포함된 모든 요소와 매칭되며, 이 속성은 공백으로 구분되므로 하나의 요소가 여러 클래스를 가질 수 있습니다. #header처럼 쓰는 id 선택자는 id 속성 값이 정확히 일치하는 단 하나의 요소와 매칭됩니다. id는 문서 내에서 유일해야 합니다.

실무 원칙은 거의 클래스로만 스타일을 지정하는 것입니다. 세 가지 중 클래스만이 재사용 가능하면서도 명시도(브라우저가 충돌을 해결할 때 사용하는 우선순위 점수로, 이 장 끝에서 다룹니다)가 낮아서, 스타일을 평평하고 덮어쓰기 쉬운 상태로 유지해 줍니다. 타입 선택자는 리셋, body의 기본 글꼴, ainput의 적절한 기본값처럼 소수의 기본 규칙에만 어울립니다. id는 JavaScript의 훅이나 프래그먼트 링크 대상(URL의 #pricing)으로는 정당하지만, 스타일링 훅으로 쓰면 함정이 됩니다. 유일하기 때문에 그룹을 표현할 수 없고, 명시도가 높아서 나중에 나온 클래스 규칙이 별도의 승부수 없이는 이를 덮어쓸 수 없습니다. 스타일은 클래스로, id는 앵커와 스크립트를 위해 남겨두세요.

html
<button class="btn-primary">가입하기</button>
<button class="btn-primary">지금 구매</button>
css
/* 타입 선택자: 페이지의 모든 버튼 */
button {
  font-family: inherit;
}

/* 클래스 선택자: btn-primary 태그가 붙은 요소만 */
.btn-primary {
  background: rebeccapurple;
  color: white;
}

두 버튼 모두 같은 클래스를 공유하기 때문에, 하나의 규칙에서 .btn-primary 스타일을 받아옵니다. 바로 이 재사용성이 클래스를 즐겨 쓰는 이유입니다. 이는 CSS는 어떻게 동작하는가에서 다룬 내용, 즉 각 규칙이 선택자와 선언 집합의 짝이라는 개념과 바로 이어집니다.

Juno타입, 클래스, id 선택자p 같은 타입 선택자는 그 종류의 모든 요소를 건드리는데, 대부분의 경우 필요한 것보다 범위가 넓습니다. 클래스는 신경 쓰고 싶은 요소에만 붙이는 명찰이고, .btn-primary처럼 점을 붙여서 스타일을 지정합니다. 거의 모든 경우에 클래스부터 시작하면 크게 틀리지 않습니다.
Juno타입, 클래스, id 선택자 클래스가 기본 도구입니다. 재사용 가능하고, 하나의 요소가 여러 클래스를 걸칠 수 있죠. 타입 선택자는 body 글꼴 같은 넓은 기본 스타일에만 쓰고, id는 스타일링에 쓰기엔 너무 구체적이고 너무 단발성이라고 생각하세요. 나중에 겪을 골칫거리를 미리 피하려면 클래스부터 손이 가야 합니다.
Juno타입, 클래스, id 선택자 클래스는 기본 선택자 중 재사용 가능하면서도 명시도가 낮은 유일한 존재이고, 그래서 클래스로 스타일을 짜고 규칙을 평평하게 유지하는 것입니다. 타입 선택자는 소수의 기본 레이어에서 제 역할을 하고, id는 스크립트와 링크 앵커에 속할 뿐 스타일링에는 속하지 않습니다. 언젠가 id 규칙이 나중에 나온 클래스로도 덮어써지지 않는 날, id를 멀리한 자신에게 감사하게 될 것입니다.

결합자: 관계로 선택하기

가끔은 어떤 요소가 다른 요소 안에 들어 있을 때만 스타일을 주고 싶을 때가 있습니다. 내비게이션 안의 링크는 문단 속 링크와 다르게 보여야 하지만, 둘 다 <a> 요소라는 점은 같습니다. 이런 관계는 **결합자**로 표현합니다. 두 선택자를 공백으로 나란히 쓰면, CSS는 이를 "두 번째 것을, 첫 번째 것 안에 있을 때만"으로 해석합니다.

그래서 .nav a는 "nav 클래스를 가진 요소 안에 있는 모든 링크"를 뜻합니다. 여기서 공백은 실제로 의미를 가진 문법입니다. 보기 좋게 넣는 여백이 아니라, "안쪽을 살펴보라"는 지시입니다.

**결합자**는 두 선택자를 연결해 HTML상에서 요소들이 맺는 관계를 기준으로 매칭합니다. 가장 흔한 것은 자손 결합자로, 공백으로 씁니다. .nav a.nav 안에 아무리 깊이 있어도 그 안의 모든 <a>와 매칭됩니다.

더 깊은 자손이 아니라 직계 자식만 원할 때는 자식 결합자 >를 씁니다. .menu > li.menu의 직접적인 자식인 list item만 매칭하고, 중첩된 목록은 건너뜁니다. 부모를 공유하는 요소를 위한 형제 결합자도 두 가지 있습니다. 인접 형제 결합자 +는 바로 뒤에 오는 요소와 매칭되고(h2 + p는 제목 바로 다음 문단을 스타일링합니다), 일반 형제 결합자 ~는 뒤에 오는 모든 형제와 매칭됩니다. 자손과 자식 결합자가 실무의 대부분을 처리하고, 형제 결합자는 여백 조정이나 폼 패턴에서 유용합니다.

**결합자**는 두 선택자 사이에 놓여, 그 둘이 만족해야 할 구조적 관계를 정의하는 문자입니다. 자손 결합자(공백)는 오른쪽 선택자가 왼쪽 선택자 안에 어떤 깊이로든 중첩되어 있을 때 매칭됩니다. 자식 결합자(>)는 이를 직접 자식으로만 제한합니다. .menu > li는 중첩된 .submenu 안에 묻힌 list item은 무시합니다. 인접 형제 결합자(+)는 같은 부모를 가진 요소 중 바로 뒤에 오는 요소와 매칭되고, 일반 형제 결합자(~)는 같은 부모를 가진 뒤쪽의 모든 형제와 매칭됩니다.

실무에서 기억해 둘 만한 점이 두 가지 있습니다. 첫째, 복합 선택자는 브라우저의 매칭 엔진에서 오른쪽에서 왼쪽으로 읽힙니다. .nav a의 경우 엔진은 먼저 <a> 요소를 찾은 다음, 각각 위로 올라가며 .nav 조상이 있는지 확인합니다. 그래서 자손 체인이 길어질수록 비용이 커지고, 느슨한 .nav a보다 촘촘한 .nav > a가 더 저렴할 수 있습니다. 둘째, 자손 선택자는 CSS를 마크업 구조에 결속시킵니다. .card .title은 누군가 title을 다른 요소로 감싸는 순간 깨져버립니다. 요소에 직접 클래스를 붙이는 방식(.card-title)을 선호하면 마크업이 바뀌어도 스타일이 잘 견디고, 정말로 표현하고 싶은 것이 관계 자체일 때만 결합자를 쓰면 됩니다.

css
/* 자손: nav 안에 있는 모든 링크 */
.nav a {
  text-decoration: none;
}

/* 자식: .menu의 직계 자식인 list item만 */
.menu > li {
  display: inline-block;
}

첫 번째 규칙은 nav 안에 중첩된 모든 링크에 닿고, 두 번째 규칙은 메뉴의 최상위 항목에만 닿아 중첩된 목록은 그대로 둡니다. 둘 중 무엇을 고를지는 규칙이 얼마나 깊이 내려가길 바라는지를 고르는 일입니다.

Juno결합자 두 선택자 사이의 공백은 "안에"라는 뜻입니다. .nav a는 nav 안에 있는 링크를 스타일링합니다. 이 공백은 보기 좋게 넣는 여백이 아니라 지시문이므로, 실수로 빼먹지 마세요. 이 패턴 하나로 일상적인 스타일링의 상당 부분을 처리할 수 있습니다.
Juno결합자 공백은 자손 결합자이며 어떤 깊이든 매칭되고, >는 그것을 직계 자식만으로 좁힙니다. 형제 결합자 +~는 앞에 오는 것을 기준으로 요소를 스타일링해야 할 때 씁니다. 자손과 자식 결합자면 평소에 쓰는 것 대부분이 해결됩니다.
Juno결합자 결합자는 유용하지만 CSS를 HTML 구조에 묶어버리기도 하므로, .card .title처럼 긴 자손 체인은 리팩터링 한 번에 깨질 수 있습니다. 요소에 직접 클래스를 붙이는 쪽을 우선하고, 정말로 표현하려는 것이 관계 자체일 때만 결합자를 쓰세요. 매칭이 오른쪽에서 왼쪽으로 이루어진다는 점도 있어서, 촘촘한 >가 느슨한 자손 체인보다 나은 경우가 많습니다.

가상 클래스: 상태로 스타일링하기

요소는 여러 상태에 놓일 수 있습니다. 링크는 가만히 있을 수도 있고 마우스가 위에 올라가 있을 수도 있죠. **가상 클래스**를 쓰면 요소의 상태를 기준으로 스타일을 지정할 수 있으며, 선택자 뒤에 콜론 하나를 붙여 씁니다.

가장 먼저 쓰게 될 것은 :hover입니다. .btn-primary:hover라고 쓰면 포인터가 버튼 위에 있을 때만 그 스타일이 적용되며, 버튼이나 링크에 마우스를 갖다 대면 반응이 살짝 일어나는 것도 바로 이 덕분입니다.

**가상 클래스**는 특정 상태나 위치에 있는 요소를 지정하며, 콜론 하나로 씁니다. 포인터가 위에 있을 때는 :hover, 키보드 입력을 위해 선택되었을 때는 :focus, 부모 안에서 첫 번째 요소일 때는 :first-child입니다.

:focus는 신경 써서 봐야 합니다. 이건 부가 기능이 아니라 접근성과 직결된 문제이기 때문입니다. 키보드로 탐색하는 사람들은 탭 키로 한 컨트롤에서 다음 컨트롤로 이동하는데, :focus 스타일은 자신이 지금 어디에 있는지 알려주는 유일한 신호입니다. 눈에 보이는 대체 표시 없이 포커스 아웃라인을 절대 제거하지 마세요. :nth-child()처럼 괄호 안에 패턴을 받는 구조적 가상 클래스도 있습니다. :nth-child(odd)는 한 요소씩 건너뛰며 매칭되어 표의 줄무늬를 만들 때 유용합니다. :first-child:last-child는 별도의 클래스 없이도 그룹의 양 끝을 잡아냅니다.

가상 클래스(콜론 하나 뒤에 오는 키워드)는 별도의 마크업 없이도 특정 상태나 구조적 위치에 있는 요소와 매칭됩니다. 상태 가상 클래스로는 :hover, :active, :focus가 있고, 구조적 가상 클래스로는 :first-child, :last-child, 그리고 매개변수를 받는 :nth-child()가 있습니다. :nth-child()의 인자는 an+b 형태의 패턴입니다(:nth-child(2n)은 두 번째 요소마다, :nth-child(3n+1)은 세 개짜리 그룹마다 첫 번째 요소를 뜻합니다).

실무에서 짚어둘 점이 두 가지 있습니다. :focus는 키보드 접근성을 위해 타협할 수 없는 요소입니다. 키보드 사용자는 포커스 링을 보고 자신의 위치를 파악하므로, 눈에 보이는 대체 표시 없이 아웃라인을 없애면 그들에게는 인터페이스를 쓸 수 없게 됩니다. :focus-visible을 쓰는 것이 낫습니다. 이는 키보드 상호작용에서는 링을 보여주지만 마우스 클릭에서는 숨겨주므로, 마우스 사용자에게는 깔끔한 경험을, 키보드 사용자에게는 접근 가능한 경험을 동시에 줄 수 있습니다. 구조적인 부분에서는 :nth-child(n)이 종류를 가리지 않고 모든 형제를 세는 반면, :nth-of-type(n)은 같은 종류의 형제만 센다는 점을 기억하세요. 이 차이 때문에 목록에 낯선 요소가 하나 섞이면, 제대로 동작하는 규칙과 조용히 잘못 셈하는 규칙이 갈립니다.

css
.btn-primary:hover {
  background: purple;
}

/* 키보드 사용자에게는 눈에 보이는 포커스 표시가 필요합니다 */
.btn-primary:focus-visible {
  outline: 2px solid rebeccapurple;
  outline-offset: 2px;
}

첫 번째 규칙은 포인터가 버튼 위에 있을 때 발동되고, 두 번째 규칙은 버튼이 키보드로 선택되었을 때 명확한 링을 그려주는데, 이것이 마우스 없이도 페이지를 사용할 수 있게 해주는 요소입니다. 여기서 색과 아웃라인을 고르는 방식은 CSS는 어떻게 동작하는가와 어떤 속성에든 값을 정하는 방식으로 이어집니다.

Juno가상 클래스 가상 클래스는 콜론 하나로 특정 상태에 있는 요소를 스타일링합니다. 포인터가 위에 있을 때를 뜻하는 :hover가 대표적이죠. 버튼의 호버 효과가 바로 여기서 나옵니다. 선택자에 바로 붙이며, 사이에 공백은 없습니다.
Juno가상 클래스 가상 클래스는 상태와 위치를 매칭합니다: :hover, :focus, :nth-child(), :first-child. :focus는 접근성 요구사항으로 다루세요. 키보드 사용자는 자신이 어디에 있는지 봐야 하니, 눈에 보이는 대체 표시 없이 아웃라인을 없애는 일은 절대 하지 마세요. 이 습관 하나로 모두가 편하게 쓸 수 있는 페이지를 만들 수 있습니다.
Juno가상 클래스:focus 대신 :focus-visible을 쓰면 키보드 사용자에게는 링이 보이고 마우스 클릭에서는 보이지 않게 됩니다. 포커스 표시를 아예 없애버린 인터페이스는 절대 배포하지 마세요. :nth-child:nth-of-type의 함정도 조심하세요. 하나는 모든 형제를 세고, 다른 하나는 같은 종류의 형제만 세는데, 잘못 고르면 목록에 낯선 요소가 하나 섞이는 순간 셈이 틀어집니다. 문제없어 보이다가 갑자기 터지는 종류의 버그입니다.

가상 요소: 요소의 일부분 스타일링하기

가상 클래스는 특정 상태에 있는 요소 전체를 스타일링합니다. **가상 요소**는 다릅니다. 요소의 일부분만 스타일링하거나, HTML에는 전혀 없는 작은 콘텐츠를 추가할 수 있게 해줍니다.

가장 먼저 접하게 될 두 가지는 ::before::after입니다. 이들은 요소의 시작이나 끝에 콘텐츠를 삽입하며, content 속성으로 무엇을 삽입할지 지정합니다. 콜론이 두 개라는 점에 주목하세요. 이 이중 콜론이 가상 요소를 가상 클래스와 구분하는 표시입니다.

**가상 요소**는 요소 전체가 아니라 특정 부분을 스타일링하며, 콜론 하나짜리 가상 클래스와 구별하기 위해 이중 콜론(::)을 씁니다. 가장 많이 쓰이는 것은 ::before::after로, 요소의 맨 시작이나 맨 끝에 생성된 자식 요소를 만들어냅니다. 이들은 렌더링을 위해 content 속성이 필요합니다. 비어 있어도 상관없습니다(content: ""). 그리고 이 생성된 콘텐츠는 장식용입니다. 스크린 리더가 건너뛸 수도 있으니, 의미 있는 내용을 여기에 넣지 마세요.

또 하나 흔한 것은 ::placeholder로, 빈 입력 필드 안에 있는 흐릿한 힌트 텍스트를 스타일링합니다. 가상 요소는 요소의 한 조각을 대상으로 하므로, 마크업에 태그를 추가하지 않고도 작은 시각적 요소(레이블 앞 아이콘, 제목 뒤 장식용 선 같은 것)를 만들 수 있게 해줍니다.

**가상 요소**는 자체 태그가 없는 요소의 일부를 드러내며, 이중 콜론 키워드로 스타일을 지정합니다. ::before::after는 요소의 첫째 자식이나 마지막 자식으로 인라인 박스를 생성합니다. 이들은 content가 설정되어야만 렌더링되며(순전히 장식용 박스라면 content: ""를 씁니다), 이렇게 생성된 콘텐츠는 접근성 트리 바깥에 위치하므로 반드시 장식용으로만 남아야 하고 사용자가 알아야 하는 의미를 담아서는 안 됩니다. ::placeholder는 입력 필드의 힌트 텍스트를, ::selection은 하이라이트된 텍스트를, ::first-line::first-letter는 타이포그래피적 조각을 대상으로 합니다.

콜론 하나와 두 개의 이야기는 알아둘 만합니다. 현대의 가상 요소는 가상 클래스와 구분하기 위해 ::(콜론 두 개)를 쓰는데, 이는 CSS3에서 도입된 구분법입니다. 원래부터 있던 네 개의 가상 요소(:before, :after, :first-line, :first-letter)에 대해서는 하위 호환을 위해 콜론 하나짜리 표기도 여전히 파싱되지만, 새 코드에서는 이중 콜론 표기를 쓰세요. 콜론 하나짜리는 호환성을 위한 유물일 뿐, 가져다 쓸 만한 스타일이 아닙니다. 실무에서는 ::before::after가 툴팁, 커스텀 목록 마커, 오래된 레이아웃의 clearfix, 그리고 그대로 HTML에 넣으면 지저분해질 장식적 요소들을 조용히 처리해 줍니다.

css
/* 모든 nav 링크 뒤에 장식용 화살표를 추가합니다 */
.nav a::after {
  content: " →";
  color: rebeccapurple;
}

/* 검색창 안의 힌트 텍스트를 스타일링합니다 */
.search-input::placeholder {
  color: grey;
  font-style: italic;
}

::after 규칙은 HTML에는 존재하지 않고 스타일에서만 살아 있는 화살표를 삽입하고, ::placeholder는 브라우저가 그려주는 텍스트에 손을 뻗어 스타일을 지정합니다. 둘 다 평범한 선택자로는 손댈 수 없는 대상에 스타일을 입힙니다. 힌트 텍스트나 레이블을 스타일링하고 있다면, 타이포그래피 장에서 텍스트 쪽을 더 깊이 다룹니다.

Juno가상 요소 가상 요소는 요소의 일부를 스타일링하거나 작은 콘텐츠를 추가하며, ::before::after처럼 콜론 두 개를 씁니다. 이 둘은 나타나려면 content 속성이 있어야 합니다. 콜론이 두 개인지 보면 가상 요소와 가상 클래스를 빠르게 구분할 수 있습니다.
Juno가상 요소 가상 요소는 이중 콜론을 쓰며 요소의 한 조각을 스타일링합니다: ::before, ::after, ::placeholder. 앞의 두 개는 값이 필요합니다. 빈 값 ""이라도 상관없지만, 이 콘텐츠는 장식용이므로 진짜 의미는 HTML에 남겨두세요. 이들 덕분에 장식 하나 걸려면 하려던 태그 추가를 안 해도 됩니다.
Juno가상 요소 가상 요소는 콜론 두 개로 쓰세요. 콜론 하나짜리 표기는 원래 네 개에 대해서만 하위 호환을 위해 남아 있을 뿐입니다. ::before::after의 콘텐츠는 접근성 트리 바깥에 있으므로, 장식용으로만 남고 사용자가 읽어야 하는 내용은 절대 담지 않아야 합니다. 이들은 툴팁, 커스텀 마커, 그리고 마크업을 부풀렸을 장식들을 조용히 처리해 줍니다.

명시도: 어느 규칙이 이기는가

언젠가는 두 규칙이 같은 요소를 서로 다른 방식으로 스타일링하려고 부딪히게 됩니다. 하나는 텍스트가 파란색이라 하고, 다른 하나는 빨간색이라 합니다. 브라우저는 둘 중 하나를 골라야 하는데, 이때 결정하는 기준을 **명시도**라고 부릅니다. 더 구체적인 선택자가 이깁니다.

간단히 말하면 강도의 순서가 있습니다. id 선택자는 클래스 선택자를 이기고, 클래스 선택자는 타입 선택자를 이깁니다. 그래서 #header.title이 같은 요소에 색을 각각 지정하면, id가 이깁니다. 두 규칙의 명시도가 같다면, 파일에서 나중에 적힌 규칙이 이깁니다.

여러 규칙이 같은 요소를 대상으로 같은 속성을 지정할 때, 브라우저는 관련된 선택자의 종류에 따른 점수인 **명시도**로 충돌을 해결합니다. 강한 순서부터 약한 순서로는 인라인 스타일(요소의 style 속성), 그다음 id, 그다음 클래스(가상 클래스와 속성 선택자도 포함), 그다음 타입 선택자(가상 요소도 포함)입니다. 더 구체적인 선택자는 소스에서의 순서와 무관하게 이기고, 명시도가 같을 때에만 나중에 나온 규칙이 이깁니다.

바로 이 점 때문에 클래스에 의존하고 id를 스타일링에는 피하는 것이 실용적입니다. id로 스타일을 지정하면, 나중에 나온 클래스 규칙이 그것을 덮어쓸 수 없어 점점 더 강한 수단으로 올라가게 됩니다. 그리고 어떤 선언 뒤에 붙여서 다른 모든 것을 이기게 만드는 !important는 최후의 수단으로만 다루세요. 이는 일반적인 캐스케이드를 깨뜨리며, !important 하나를 이길 수 있는 유일한 방법은 또 다른 !important뿐이니, 이건 시작하고 싶지 않은 악순환입니다.

**명시도**는 브라우저가 서로 충돌하는 선언 중 하나를 선택하는 데 쓰는 알고리즘입니다. 흔히 (a, b, c)로 표기하는 세 부분짜리 카운트로 계산됩니다. a는 id 선택자의 개수, b는 클래스, 속성, 가상 클래스 선택자의 개수, c는 타입과 가상 요소 선택자의 개수입니다. 각 부분은 왼쪽에서 오른쪽으로 비교되므로, id 하나는 몇 개의 클래스가 있든 그것들을 이기고, 클래스 하나는 몇 개의 타입 선택자가 있든 그것들을 이깁니다. #header .title a(1, 1, 1)로 점수가 매겨지고, .nav .menu .item a(0, 3, 1)로 매겨집니다. 그래서 전자가 id 하나만으로 이깁니다. 인라인 style 속성은 모든 선택자 명시도보다 위에 있고, !important(값 뒤에 붙여서 강제로 이기게 만드는 플래그)는 그것마저도 압도합니다. 전체 선택자 *와 결합자는 점수에 아무것도 더하지 않습니다.

현대적인 도구 두 가지가 이 관리 방식을 바꿔놓습니다. :is()는 선택자 목록을 받아 그중 하나라도 매칭되면 매칭되지만, 명시도는 인자 중 가장 구체적인 것을 따르므로, :is(#header, .title)은 id의 무게를 그대로 물려받습니다. :where()는 같은 방식으로 매칭하지만 명시도는 언제나 0이어서, 무엇으로든 덮어쓸 수 있는 가벼운 기본 스타일을 작성할 때 쓰기 좋은 도구입니다. 관계형 가상 클래스인 :has()는 무언가를 포함하는 요소와 매칭되며("이미지를 가진 .card", .card:has(img)), 인자의 명시도를 그대로 가져갑니다. 이 모든 것 아래 깔린 유지보수 원칙은 명시도를 낮고 평평하게 유지하는 것입니다. 단일 클래스 선택자를 선호하고, id와 !important를 피하고, 기본값을 양보하고 싶은 곳에서는 :where()로 무게를 0으로 만드세요. 명시도가 평평하면 나중에 나온 규칙이 나중에 나왔다는 사실만으로 항상 이길 수 있고, 이는 캐스케이드가 여러분을 막지 않고 도와주는 상태입니다.

css
/* 타입 선택자, 명시도 (0, 0, 1) */
p {
  color: grey;
}

/* 클래스 선택자, 명시도 (0, 1, 0): 이것이 이깁니다 */
.intro {
  color: black;
}
html
<p class="intro">이 문단은 클래스 규칙이 타입 규칙보다 우선하기 때문에 검은색입니다.</p>

두 규칙 모두 이 문단을 대상으로 하고 둘 다 color를 지정하지만, 클래스 선택자가 타입 선택자보다 명시도가 높으므로 문단은 검은색이 됩니다. 선택자 하나를 명시도 사다리에서 위나 아래로 살짝 옮기는 것만으로도, 더 무거운 도구를 쓰지 않고 충돌을 해결할 수 있는 경우가 많습니다. 박스 모델 장에서도 간격 규칙이 충돌할 때 같은 캐스케이드 문제를 마주칩니다.

Juno명시도 두 규칙이 같은 요소를 두고 다툴 때는 더 구체적인 쪽이 이깁니다. id는 클래스를 이기고, 클래스는 타입 선택자를 이깁니다. 명시도가 같다면 파일에서 나중에 나온 쪽이 이깁니다. 대부분의 충돌은 이걸로 설명됩니다.
Juno명시도 강도는 인라인, id, 클래스, 타입 순으로 이어지고, 더 구체적인 규칙은 파일 안 어디에 있든 이깁니다. 그래서 클래스로 스타일링하고 id를 피하면 CSS를 계속 덮어쓸 수 있게 유지할 수 있습니다. !important는 최후의 수단으로 남겨두세요. 이걸 이길 수 있는 유일한 방법은 또 다른 !important뿐이니까요.
Juno명시도 명시도는 id, 클래스, 타입 각각에 대해 (a, b, c)로 세고 왼쪽부터 비교하므로, id 하나가 클래스 무더기를 다 이깁니다. 낮고 평평하게 유지하세요. 단일 클래스, 스타일링에는 id를 쓰지 않기, !important 없이, 그리고 기본값을 양보하고 싶을 때는 :where()로 무게를 0으로 만들기. 명시도가 평평하면 나중에 나온 규칙이 나중에 나왔다는 이유만으로 항상 이길 수 있고, 그것이 캐스케이드가 여러분과 함께 일하는 방식입니다.