CSS 선택자


페이지에는 온갖 요소가 가득하고 적용하고 싶은 스타일도 준비되어 있지만, 그 색이나 글꼴이 어떤 요소에 속하는지 지정하지 않으면 아무 소용이 없습니다. 선택자는 바로 그 지정을 위한 도구입니다. 이 문단, 모든 버튼, nav 안에 있는 링크만 골라내는 것처럼 말이죠. 선택자를 능숙하게 다루게 되면 CSS의 나머지 부분은 그저 무엇을 바꿀지 고르는 문제가 됩니다. 어떻게 그 요소에 도달할지는 이미 알고 있으니까요.
타입, 클래스, id 선택자
CSS 규칙은 두 부분으로 이루어집니다. 스타일을 적용할 요소를 고르는 **선택자**와, 어떻게 스타일을 적용할지 적어 놓은 선언 블록입니다. 가장 단순한 선택자는 요소의 이름 그 자체를 쓰는 것으로, 타입 선택자라고 부릅니다. p라고 쓰면 페이지의 모든 문단에 스타일이 적용됩니다.
하지만 이건 대부분 범위가 너무 넓습니다. 모든 문단이 똑같은 모습이길 바라는 경우는 드물기 때문이죠. 그래서 HTML의 class 속성을 이용해 요소에 클래스라는 이름표를 붙이고, CSS에서는 그 이름표 앞에 점을 붙여 지정합니다. 클래스를 명찰이라고 생각해 보세요. 같은 그룹으로 다루고 싶은 요소마다 같은 명찰을 달아두고, 그 명찰 하나에 스타일을 한 번만 정해주는 것입니다.
<button class="btn-primary">가입하기</button>
<button class="btn-primary">지금 구매</button>/* 타입 선택자: 페이지의 모든 버튼 */
button {
font-family: inherit;
}
/* 클래스 선택자: btn-primary 태그가 붙은 요소만 */
.btn-primary {
background: rebeccapurple;
color: white;
}두 버튼 모두 같은 클래스를 공유하기 때문에, 하나의 규칙에서 .btn-primary 스타일을 받아옵니다. 바로 이 재사용성이 클래스를 즐겨 쓰는 이유입니다. 이는 CSS는 어떻게 동작하는가에서 다룬 내용, 즉 각 규칙이 선택자와 선언 집합의 짝이라는 개념과 바로 이어집니다.
p 같은 타입 선택자는 그 종류의 모든 요소를 건드리는데, 대부분의 경우 필요한 것보다 범위가 넓습니다. 클래스는 신경 쓰고 싶은 요소에만 붙이는 명찰이고, .btn-primary처럼 점을 붙여서 스타일을 지정합니다. 거의 모든 경우에 클래스부터 시작하면 크게 틀리지 않습니다. 결합자: 관계로 선택하기
가끔은 어떤 요소가 다른 요소 안에 들어 있을 때만 스타일을 주고 싶을 때가 있습니다. 내비게이션 안의 링크는 문단 속 링크와 다르게 보여야 하지만, 둘 다 <a> 요소라는 점은 같습니다. 이런 관계는 **결합자**로 표현합니다. 두 선택자를 공백으로 나란히 쓰면, CSS는 이를 "두 번째 것을, 첫 번째 것 안에 있을 때만"으로 해석합니다.
그래서 .nav a는 "nav 클래스를 가진 요소 안에 있는 모든 링크"를 뜻합니다. 여기서 공백은 실제로 의미를 가진 문법입니다. 보기 좋게 넣는 여백이 아니라, "안쪽을 살펴보라"는 지시입니다.
/* 자손: nav 안에 있는 모든 링크 */
.nav a {
text-decoration: none;
}
/* 자식: .menu의 직계 자식인 list item만 */
.menu > li {
display: inline-block;
}첫 번째 규칙은 nav 안에 중첩된 모든 링크에 닿고, 두 번째 규칙은 메뉴의 최상위 항목에만 닿아 중첩된 목록은 그대로 둡니다. 둘 중 무엇을 고를지는 규칙이 얼마나 깊이 내려가길 바라는지를 고르는 일입니다.
.nav a는 nav 안에 있는 링크를 스타일링합니다. 이 공백은 보기 좋게 넣는 여백이 아니라 지시문이므로, 실수로 빼먹지 마세요. 이 패턴 하나로 일상적인 스타일링의 상당 부분을 처리할 수 있습니다. 가상 클래스: 상태로 스타일링하기
요소는 여러 상태에 놓일 수 있습니다. 링크는 가만히 있을 수도 있고 마우스가 위에 올라가 있을 수도 있죠. **가상 클래스**를 쓰면 요소의 상태를 기준으로 스타일을 지정할 수 있으며, 선택자 뒤에 콜론 하나를 붙여 씁니다.
가장 먼저 쓰게 될 것은 :hover입니다. .btn-primary:hover라고 쓰면 포인터가 버튼 위에 있을 때만 그 스타일이 적용되며, 버튼이나 링크에 마우스를 갖다 대면 반응이 살짝 일어나는 것도 바로 이 덕분입니다.
.btn-primary:hover {
background: purple;
}
/* 키보드 사용자에게는 눈에 보이는 포커스 표시가 필요합니다 */
.btn-primary:focus-visible {
outline: 2px solid rebeccapurple;
outline-offset: 2px;
}첫 번째 규칙은 포인터가 버튼 위에 있을 때 발동되고, 두 번째 규칙은 버튼이 키보드로 선택되었을 때 명확한 링을 그려주는데, 이것이 마우스 없이도 페이지를 사용할 수 있게 해주는 요소입니다. 여기서 색과 아웃라인을 고르는 방식은 CSS는 어떻게 동작하는가와 어떤 속성에든 값을 정하는 방식으로 이어집니다.
:hover가 대표적이죠. 버튼의 호버 효과가 바로 여기서 나옵니다. 선택자에 바로 붙이며, 사이에 공백은 없습니다. 가상 요소: 요소의 일부분 스타일링하기
가상 클래스는 특정 상태에 있는 요소 전체를 스타일링합니다. **가상 요소**는 다릅니다. 요소의 일부분만 스타일링하거나, HTML에는 전혀 없는 작은 콘텐츠를 추가할 수 있게 해줍니다.
가장 먼저 접하게 될 두 가지는 ::before와 ::after입니다. 이들은 요소의 시작이나 끝에 콘텐츠를 삽입하며, content 속성으로 무엇을 삽입할지 지정합니다. 콜론이 두 개라는 점에 주목하세요. 이 이중 콜론이 가상 요소를 가상 클래스와 구분하는 표시입니다.
/* 모든 nav 링크 뒤에 장식용 화살표를 추가합니다 */
.nav a::after {
content: " →";
color: rebeccapurple;
}
/* 검색창 안의 힌트 텍스트를 스타일링합니다 */
.search-input::placeholder {
color: grey;
font-style: italic;
}::after 규칙은 HTML에는 존재하지 않고 스타일에서만 살아 있는 화살표를 삽입하고, ::placeholder는 브라우저가 그려주는 텍스트에 손을 뻗어 스타일을 지정합니다. 둘 다 평범한 선택자로는 손댈 수 없는 대상에 스타일을 입힙니다. 힌트 텍스트나 레이블을 스타일링하고 있다면, 타이포그래피 장에서 텍스트 쪽을 더 깊이 다룹니다.
::before와 ::after처럼 콜론 두 개를 씁니다. 이 둘은 나타나려면 content 속성이 있어야 합니다. 콜론이 두 개인지 보면 가상 요소와 가상 클래스를 빠르게 구분할 수 있습니다. 명시도: 어느 규칙이 이기는가
언젠가는 두 규칙이 같은 요소를 서로 다른 방식으로 스타일링하려고 부딪히게 됩니다. 하나는 텍스트가 파란색이라 하고, 다른 하나는 빨간색이라 합니다. 브라우저는 둘 중 하나를 골라야 하는데, 이때 결정하는 기준을 **명시도**라고 부릅니다. 더 구체적인 선택자가 이깁니다.
간단히 말하면 강도의 순서가 있습니다. id 선택자는 클래스 선택자를 이기고, 클래스 선택자는 타입 선택자를 이깁니다. 그래서 #header와 .title이 같은 요소에 색을 각각 지정하면, id가 이깁니다. 두 규칙의 명시도가 같다면, 파일에서 나중에 적힌 규칙이 이깁니다.
/* 타입 선택자, 명시도 (0, 0, 1) */
p {
color: grey;
}
/* 클래스 선택자, 명시도 (0, 1, 0): 이것이 이깁니다 */
.intro {
color: black;
}<p class="intro">이 문단은 클래스 규칙이 타입 규칙보다 우선하기 때문에 검은색입니다.</p>두 규칙 모두 이 문단을 대상으로 하고 둘 다 color를 지정하지만, 클래스 선택자가 타입 선택자보다 명시도가 높으므로 문단은 검은색이 됩니다. 선택자 하나를 명시도 사다리에서 위나 아래로 살짝 옮기는 것만으로도, 더 무거운 도구를 쓰지 않고 충돌을 해결할 수 있는 경우가 많습니다. 박스 모델 장에서도 간격 규칙이 충돌할 때 같은 캐스케이드 문제를 마주칩니다.

