안녕하세요! 프론트엔드 강사입니다.
오늘은 CSS의 시작이자 끝이라고 할 수 있는 '선택자(Selectors)'와 '결합자(Combinators)'에 대해 MDN 공식 문서를 통해 완벽하게 정리해 보겠습니다. HTML 요소들을 예쁘게 꾸미려면 먼저 "누구를 꾸밀 것인가?"를 정확하게 콕 집어낼 수 있어야 하겠죠? 이 문서를 다 읽고 나면 복잡한 HTML 구조 속에서도 내가 원하는 요소만 귀신같이 찾아내는 CSS 명사수가 되실 수 있을 겁니다.
제 실무 경험을 담은 꿀팁들도 가득 준비했으니 재미있게 따라와 주세요!
CSS 선택자(selectors)는 여러분이 CSS 규칙(스타일)을 적용하고 싶은 요소들의 패턴을 정의하는 데 사용됩니다. 결합자(combinators)는 이러한 선택자들 사이의 관계를 정의하죠. 다양한 선택자와 결합자를 조합하면, 요소의 타입, 속성, 상태, 또는 다른 요소와의 관계를 기반으로 여러분이 원하는 요소를 아주 정밀하게 선택하고 스타일을 입힐 수 있습니다.
CSS에는 무려 80개가 넘는 선택자와 결합자가 존재합니다! 선택할 수 있는 요소의 종류에 따라 다음과 같은 카테고리로 묶어볼 수 있습니다.
div는 모든 <div> 요소를 선택하고, input은 모든 <input> 요소를 선택합니다. *)로 표시하며, 문서 내의 모든 요소를 선택하는 특별한 타입 선택자입니다..)를 앞에 붙여서 특정 class 속성을 가진 모든 요소를 선택합니다. 예를 들어, .index는 class="index"를 가진 모든 요소와 일치합니다.#, 해시 기호)를 앞에 붙여서 특정 id 속성값을 가진 단 하나의 요소를 선택합니다. 예를 들어, #toc는 id="toc"를 가진 요소와 일치합니다.class와 id는 모두 전역 속성(global attributes)입니다. 한 문서 안에서 동일한 id를 가진 요소는 딱 하나만 존재해야 합니다. (하지만 만약 실수로 여러 개를 만들었다 하더라도, CSS의 ID 선택자는 그 id를 가진 모든 요소를 선택하긴 합니다.)
타입 선택자나 전체 선택자를 클래스나 ID 선택자와 결합하여 복합 선택자(compound selector)를 만들 때는, 반드시 타입/전체 선택자가 클래스나 ID보다 먼저 와야 합니다 (예: p.myClass).
👨🏫 강사님의 꿀팁:
실무에서는*(전체 선택자)를 사용할 때 주의해야 합니다! 모든 요소를 훑어야 하므로 브라우저 렌더링 성능을 떨어뜨릴 수 있거든요. 주로box-sizing: border-box;같은 전역 초기화 설정을 할 때만 제한적으로 쓰는 것이 좋습니다.
이 예제에서는 위에서 설명한 4가지 기본 선택자 타입을 사용해 4개의 단순 선택자(simple selectors)와 1개의 복합 선택자(compound selector)를 선언합니다.
* {
font-style: italic; /* 문서의 모든 글씨를 기울입니다 */
}
p {
color: red; /* 모든 p 태그 글씨를 빨갛게 만듭니다 */
}
.myClass {
text-decoration: underline; /* myClass를 가진 요소에 밑줄을 긋습니다 */
}
#myId {
font-family: monospace; /* myId를 가진 요소의 폰트를 바꿉니다 */
}
p.myClass#myId {
font-size: 1.5rem; /* p태그이면서 클래스가 myClass이고 아이디가 myId인 녀석만 글씨를 키웁니다! */
}
<p class="myClass" id="myId">I match everything.</p>
<p>I match the universal and type selectors only.</p>
단일 선택자만으로는 원하는 요소를 정확히 짚어내기 어려울 때가 많습니다. 이때 CSS 결합자를 사용하면, 문서 노드 트리 내에서 다른 요소와의 관계(Relationship)를 기반으로 요소를 선택할 수 있습니다. 결합자를 통해 선택자들을 엮어놓은 것을 복잡한 선택자(complex selectors)라고 부릅니다.
[공백(space)] 으로 표시합니다. 첫 번째 요소의 '자손'인 모든 노드를 선택합니다. 자식이든, 손자든, 증손자든 그 안에 포함되어 있기만 하면 다 찾아냅니다.
div span은 <div> 요소 내부에 있는 모든 <span> 요소를 선택합니다.> (크다 기호)로 표시합니다. 자손 결합자보다 훨씬 까다롭습니다. 오직 첫 번째 요소의 '직계 자식(바로 한 단계 아래)'인 노드만 선택합니다.
div > span은 <div>의 직접적인 자식인 <span> 요소만 선택합니다. div 안의 p 안에 있는 span은 선택되지 않아요!~ (물결표)로 표시합니다. 부모에서 자식으로 내려가는 게 아니라, 같은 부모를 가진 '형제(sibling)' 요소들을 찾을 때 씁니다. A ~ B라고 적으면, A 요소 뒤에 따라오는 모든 B 요소를 선택합니다 (바로 뒤에 붙어있지 않아도 됩니다).
h2 ~ p는 <h2> 뒤에 나오는 모든 <p> 요소를 선택합니다.+ (더하기 기호)로 표시합니다. 일반 형제 결합자와 비슷하지만, 훨씬 엄격합니다. A + B는 A 요소 바로 직후에 딱 붙어서 나오는 B 요소 단 하나만 선택합니다.
h2 + p는 <h2> 요소 바로 다음에 딱 붙어있는 첫 번째 <p> 요소만 선택합니다.👨🏫 강사님의 꿀팁:
이+결합자는 실무에서 폼(Form) 유효성 검사할 때 정말 자주 씁니다!
input:invalid + .error-msg { display: block; }처럼 써서, input에 잘못된 값이 들어가면 그 바로 밑에 있는 에러 메시지만 딱 나타나게 하는 마법 같은 코드를 짤 수 있답니다.
|| (이중 파이프): 특정 열(column)에 속한 노드를 선택할 때 씁니다. (예: col || td)| (단일 파이프): XML 기반 문서(SVG, MathML 등)에서 특정 네임스페이스 안에 있는 요소만 선택할 때 사용합니다. 일반적인 웹 개발에서는 자주 쓰이지 않습니다.다양한 결합자를 조합한 예제를 살펴봅시다.
h2 + p ~ p {
font-style: italic; /* h2 바로 다음 p, 그 뒤에 나오는 p들을 기울입니다 */
}
h2 + p + p {
color: red; /* h2 다음 p, 그다음 바로 붙어있는 p를 빨갛게 합니다 */
}
.myClass + p {
text-decoration: underline;
}
#myId > .myClass {
outline: 3px dashed red; /* #myId의 직계 자식 중 .myClass만 선택! */
}
* > p {
font-size: 1.1rem;
}
<h2 class="myClass" id="myId">
No selectors match. <span class="myClass">This span has an outline</span> as it is both myClass and a child of #myId.
</h2>
<p>The first paragraph is underlined. All the paragraphs are 1.1rem.</p>
<p>The second paragraph is red. This and the following paragraphs are italic.</p>
<p>The third paragraph is NOT red. It is italic and 1.1rem.</p>
<p class="myClass">Does not have an outline; this is a sibling of H2, not a child.</p>
방금 위에서 본 복잡한 결합자들은 최신 스펙인 CSS 중첩 (CSS nesting)을 사용하면 훨씬 읽기 쉽고 구조적으로 작성할 수 있습니다. (Sass나 SCSS를 써보신 분들에겐 매우 익숙할 거예요!)
위의 코드를 네이티브 CSS Nesting 기법을 사용해서 다시 작성해 보았습니다. 기능은 100% 동일하지만 코드가 부모-자식 관계로 예쁘게 묶인 걸 볼 수 있습니다.
h2 {
& + p {
& ~ p {
font-style: italic;
}
& + p {
color: red;
}
}
}
.myClass {
& + p {
text-decoration: underline;
}
}
#myId {
& > .myClass {
outline: 3px dashed red;
}
}
* {
& > p {
font-size: 1.1rem;
}
}
속성 선택자(Attribute selectors)는 태그 이름이나 클래스가 아니라, 요소가 가진 특정 HTML 속성(attribute)이나 그 속성의 값을 기준으로 요소를 쏙쏙 골라냅니다. 대괄호 [] 안에 작성합니다.
[type]: type이라는 속성을 가지기만 하면 무조건 선택합니다 (값이 뭐든 상관없음).[type="submit"]: type 속성값이 정확히 "submit"인 요소만 선택합니다.속성값의 대소문자 구분은 언어의 특성에 따라 다릅니다. 일반적인 HTML에서는 열거형(enumerated) 속성의 경우 대소문자를 구분하지 않지만, class, id, data-* 속성 등은 철저하게 대소문자를 구분합니다. 만약 대소문자를 무시하고 싶다면 선택자 끝에 i 식별자를 붙여주면 됩니다 (예: [class*="btn" i]).
👨🏫 강사님의 꿀팁:
이 기능은 특정 링크만 골라낼 때 최고입니다!
외부로 나가는 링크 아이콘을 달고 싶다?a[href^="http"](href가 http로 시작하는 a 태그)를 쓰시면 되고요,
PDF 다운로드 링크를 빨갛게 칠하고 싶다?a[href$=".pdf"](href가 .pdf로 끝나는 a 태그)를 쓰시면 됩니다. 정규표현식 같아서 정말 유용해요!
가상 클래스(Pseudo-classes)는 문서 트리(HTML)에는 존재하지 않지만, 브라우저가 알고 있는 요소의 특정 '상태(state)'를 기반으로 스타일을 입힐 때 씁니다. 콜론(:) 하나를 접두사로 사용합니다.
:visited), 요소에 마우스를 올렸는지(:hover), 체크박스에 체크가 되었는지(:checked) 등을 감지해서 스타일을 바꿀 수 있습니다.가상 클래스는 표시 상태, 사용자 입력, 시간 차원, DOM 트리 구조 등 다양한 카테고리로 나뉘며, 무려 60개가 넘게 존재합니다! 여러 가상 클래스를 조합할 수도 있으며(a:hover:visited), 타입 선택자나 클래스 뒤에 붙여서 사용합니다.
가상 요소(Pseudo-elements)는 아예 HTML에 존재하지 않는 새로운 가짜 요소(entity)를 CSS를 통해 화면에 만들어 내거나, 요소의 아주 특정한 일부(첫 글자, 첫 줄 등)만을 선택할 때 사용합니다. 콜론 두 개(::)를 접두사로 사용합니다.
::marker, 문단의 첫 번째 줄만 가리키는 ::first-line, 혹은 빈 공간을 만들어 아이콘을 넣을 수 있는 ::before, ::after 등이 있습니다.👨🏫 강사님의 꿀팁:
:hover(마우스 오버 상태)는 가상 클래스(Class)이므로 콜론 1개!
::before(새로 만들어낸 가짜 박스)는 가상 요소(Element)이므로 콜론 2개!
이 차이점을 묻는 질문이 프론트엔드 면접에서 꽤 자주 나오니 꼭 구분해서 외워두세요!
| 명세서 (Specification) |
|---|
| Selectors Level 4 |
| CSS Pseudo-Elements Module Level 4 |
이 페이지가 도움이 되었나요? (Was this page helpful to you?)
[예 (Yes)]
[아니요 (No)]
이 페이지는 MDN 기여자들에 의해 2025년 11월 7일에 마지막으로 수정되었습니다.
수고하셨습니다! CSS 선택자는 마치 외계어처럼 보일 수도 있지만, 오늘 배운 기본 기호( , >, +, ~, [], :, ::)들의 의미만 잘 기억해두시면 어떤 복잡한 구조의 HTML이라도 거뜬히 스타일링하실 수 있을 겁니다. 다음 프로젝트에 바로 적용해 보세요!