CSS를 사용하다 보면 가장 먼저 마주치는 도전 과제는 스타일을 적용할 특정 요소를 정확하게 선택하는 것입니다. 복잡한 웹 페이지에서 원하는 요소만 골라내는 것은 마치 모래사장에서 특정 모양의 조개껍데기를 찾는 것처럼 느껴질 수 있습니다. 하지만 CSS 선택자(Selectors)를 제대로 이해하고 활용한다면, 아무리 복잡한 HTML 구조에서도 필요한 요소를 정확히 타겟팅할 수 있습니다. 이 글에서는 CSS 선택자의 기본부터 고급 기법까지 완벽하게 정리해 보겠습니다.
기본 선택자
CSS 선택자의 기본 중의 기본부터 시작해봅시다. 이 선택자들은 CSS를 막 시작한 분들도 쉽게 이해할 수 있는 것들입니다.
전체 선택자(Universal Selector)
별표(*)를 사용하여 모든 요소를 선택합니다.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
모든 요소에 기본 스타일을 적용할 때 유용하지만, 성능에 영향을 줄 수 있으므로 신중하게 사용해야 합니다.
타입 선택자(Type Selector)
HTML 태그 이름을 사용하여 특정 유형의 모든 요소를 선택합니다.
p {
line-height: 1.6;
}
h1 {
font-size: 2rem;
}
클래스 선택자(Class Selector)
점(.)을 사용하여 특정 클래스를 가진 요소를 선택합니다. 가장 널리 사용되는 선택자입니다.
.button {
display: inline-block;
padding: 10px 20px;
}
.primary {
background-color: #3498db;
}
여러 클래스를 가진 요소를 선택할 수도 있습니다:
.button.primary {
border: none;
}
위 코드는 class="button primary"와 같이 두 클래스를 모두 가진 요소만 선택합니다.
ID 선택자(ID Selector)
해시 기호(#)를 사용하여 특정 ID를 가진 요소를 선택합니다. ID는 페이지에서 고유해야 합니다.
#header {
position: sticky;
top: 0;
}
#main-content {
margin-top: 20px;
}
속성 선택자(Attribute Selector)
대괄호([])를 사용하여 특정 속성을 가진 요소를 선택합니다.
[type] {
/* type 속성을 가진 모든 요소 */
}
[type="text"] {
/* type="text" 속성을 가진 요소 */
padding: 5px 10px;
}
[data-theme="dark"] {
/* 사용자 정의 데이터 속성 선택 */
background-color: #333;
color: white;
}
속성 값의 일부를 기준으로 선택할 수도 있습니다:
/* class 속성에 "btn"이 포함된 요소 */
[class*="btn"] {
cursor: pointer;
}
/* href 속성이 ".pdf"로 끝나는 링크 */
[href$=".pdf"] {
background-image: url('pdf-icon.png');
padding-left: 20px;
}
/* href 속성이 "https"로 시작하는 링크 */
[href^="https"] {
color: green;
}
결합자(Combinators)
결합자는 선택자 간의 관계를 정의하여 더 구체적인 요소를 선택할 수 있게 해줍니다.
자손 결합자(Descendant Combinator)
공백( )을 사용하여 특정 요소의 모든 자손(자식, 손자 등)을 선택합니다.
article p {
/* article 내부의 모든 p 요소 */
text-indent: 1em;
}
.container .item {
/* .container 클래스 요소 내부의 모든 .item 클래스 요소 */
margin: 10px;
}
자식 결합자(Child Combinator)
더 큰 기호(>)를 사용하여 특정 요소의 직접적인 자식만 선택합니다.
ul > li {
/* ul의 직접적인 자식인 li만 선택 (중첩된 li는 선택되지 않음) */
list-style-type: square;
}
.dropdown > .dropdown-menu {
display: none;
}
인접 형제 결합자(Adjacent Sibling Combinator)
더하기 기호(+)를 사용하여 특정 요소 바로 다음에 오는 형제 요소를 선택합니다.
h2 + p {
/* h2 바로 다음에 오는 p 요소 */
font-weight: bold;
}
.field + .field {
/* 연속된 .field 클래스 중 두 번째부터 선택 (간격 추가에 유용) */
margin-top: 10px;
}
일반 형제 결합자(General Sibling Combinator)
물결 기호(~)를 사용하여 특정 요소 다음에 오는 모든 형제 요소를 선택합니다.
h2 ~ p {
/* h2 다음에 오는 모든 p 요소 */
color: #666;
}
.active ~ li {
/* .active 클래스를 가진 요소 다음의 모든 li 요소 */
color: #999;
}
의사 클래스(Pseudo-classes)
의사 클래스는 요소의 특정 상태나 위치를 기반으로 요소를 선택할 수 있게 해줍니다.
사용자 액션 의사 클래스
/* 마우스 호버 시 */
a:hover {
text-decoration: underline;
}
/* 마우스 클릭 중(누르고 있는 상태) */
button:active {
transform: translateY(1px);
}
/* 포커스 상태일 때 (탭 이동 등) */
input:focus {
border-color: #3498db;
outline: none;
}
/* 방문한 링크 */
a:visited {
color: purple;
}
폼 요소 상태 의사 클래스
/* 체크된 상태의 체크박스/라디오 버튼 */
input:checked + label {
color: #3498db;
}
/* 비활성화된 입력 필드 */
input:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
/* 유효한/유효하지 않은 입력 값 */
input:valid {
border-color: green;
}
input:invalid {
border-color: red;
}
/* 선택적 입력 필드 */
input:optional {
background-color: #f9f9f9;
}
/* 필수 입력 필드 */
input:required {
border-left: 3px solid red;
}
구조적 의사 클래스
/* 첫 번째 자식 요소 */
li:first-child {
font-weight: bold;
}
/* 마지막 자식 요소 */
li:last-child {
border-bottom: none;
}
/* n번째 자식 요소 */
li:nth-child(3) {
background-color: #f5f5f5;
}
/* 홀수번째 자식 요소 */
li:nth-child(odd) {
background-color: #f9f9f9;
}
/* 짝수번째 자식 요소 */
li:nth-child(even) {
background-color: #eee;
}
/* 특정 패턴의 자식 요소: 3n은 3, 6, 9, ... */
li:nth-child(3n) {
color: red;
}
/* 특정 유형의 n번째 요소 */
p:nth-of-type(2) {
font-style: italic;
}
/* 부모의 유일한 자식 */
li:only-child {
list-style-type: none;
}
/* 해당 타입의 유일한 요소 */
p:only-of-type {
font-weight: bold;
}
/* 빈 요소 */
div:empty {
display: none;
}
기타 유용한 의사 클래스
/* 부정 선택자: 특정 선택자와 일치하지 않는 요소 */
:not(.special) {
opacity: 0.8;
}
/* 현재 타겟된 요소 (URL의 #fragment와 일치) */
:target {
animation: highlight 2s;
}
/* 루트 요소 (보통 html) */
:root {
--primary-color: #3498db;
}
/* 현재 클릭/탭 가능한 요소 */
:focus-visible {
outline: 2px dashed #3498db;
}
의사 요소(Pseudo-elements)
의사 요소는 실제 HTML에 존재하지 않는 "가상" 요소를 선택할 수 있게 해줍니다. 이중 콜론(::)으로 표시하지만, 하위 호환성을 위해 단일 콜론(:)도 허용됩니다.
기본 의사 요소
/* 요소의 첫 글자 */
p::first-letter {
font-size: 2em;
font-weight: bold;
}
/* 요소의 첫 줄 */
p::first-line {
font-variant: small-caps;
}
/* 요소의 선택된 텍스트 */
::selection {
background-color: #ffeb3b;
color: black;
}
/* 요소 내용 앞/뒤에 콘텐츠 추가 */
.quote::before {
content: '"';
font-size: 2em;
color: #ccc;
}
.quote::after {
content: '"';
font-size: 2em;
color: #ccc;
}
고급 의사 요소 활용
/* 아이콘 추가 */
.external-link::after {
content: ' ↗';
font-size: 0.8em;
}
/* 툴팁 생성 */
[data-tooltip]::after {
content: attr(data-tooltip);
display: none;
position: absolute;
background: black;
color: white;
padding: 5px;
border-radius: 3px;
}
[data-tooltip]:hover::after {
display: block;
}
/* 장식선 만들기 */
h2::before {
content: '';
display: block;
width: 50px;
height: 3px;
background-color: #3498db;
margin-bottom: 10px;
}
/* 플레이스홀더 스타일링 */
input::placeholder {
color: #999;
font-style: italic;
}
선택자 결합 및 고급 패턴
이제 앞서 배운 선택자들을 결합하여 더 강력하고 정확한 선택을 해봅시다.
여러 선택자 그룹화
쉼표(,)를 사용하여 여러 선택자에 동일한 스타일을 적용할 수 있습니다.
h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif;
}
.btn-primary, .btn-secondary, .btn-tertiary {
border-radius: 4px;
padding: 10px 20px;
}
복합 선택자
여러 선택자 유형을 결합하여 매우 구체적인 요소를 타겟팅할 수 있습니다.
/* nav 내부의 .active 클래스를 가진 a 태그 */
nav a.active {
font-weight: bold;
}
/* article 내부의 첫 번째 p 요소의 첫 글자 */
article p:first-child::first-letter {
font-size: 2em;
}
/* form 내부의 disabled 상태가 아닌 모든 input */
form input:not([disabled]) {
background-color: white;
}
/* 레이블이 있는 체크박스를 선택했을 때 레이블 스타일 변경 */
input[type="checkbox"]:checked + label {
color: #3498db;
}
/* 특정 섹션 내의 홀수번째 목록 아이템만 선택 */
.features-list li:nth-child(odd) {
background-color: #f9f9f9;
}
실용적인 선택자 패턴
실제 개발에서 유용하게 사용할 수 있는 선택자 패턴을 살펴봅시다.
/* 형제 요소 간 간격 추가 (마진 충돌 방지) */
.card + .card {
margin-top: 20px;
}
/* 제목 다음에 오는 첫 단락만 특별 스타일링 */
h2 + p {
font-size: 1.1em;
font-weight: 500;
}
/* 특정 깊이의 요소만 선택 */
.menu > li > ul > li {
/* 메뉴의 2단계 깊이 항목만 */
font-size: 0.9em;
}
/* 부모 기준 선택자 */
.parent:hover > .child {
/* 부모에 호버했을 때 자식 요소 스타일 변경 */
visibility: visible;
}
선택자 우선순위 이해하기
CSS에서 여러 규칙이 동일한 요소에 적용될 때, 어떤 규칙이 우선하는지는 "명시도(Specificity)"에 따라 결정됩니다.
명시도 계산 방법
명시도는 다음과 같은 순서로 계산됩니다 (높은 것이 우선):
- 인라인 스타일 (style 속성)
- ID 선택자 (#id)
- 클래스 선택자 (.class), 속성 선택자 ([attr]), 의사 클래스 (:hover)
- 요소 선택자 (div), 의사 요소 (::before)
예를 들어:
#nav .list li:hover {
/* 명시도: 1-2-1 (ID 1개, 클래스+의사클래스 2개, 요소 1개) */
}
body .wrapper .button {
/* 명시도: 0-2-1 (ID 0개, 클래스 2개, 요소 1개) */
}
첫 번째 선택자가 ID를 포함하기 때문에 두 번째 선택자보다 우선합니다.
!important
모든 명시도 규칙을 무시하고 최우선 적용하는 방법입니다. 남용하면 유지보수가 어려워지므로 꼭 필요한 경우에만 사용해야 합니다.
.button {
background-color: blue !important; /* 다른 모든 배경색 규칙 무시 */
}
최신 CSS 선택자
CSS의 최신 사양에서 도입된 몇 가지 강력한 선택자를 살펴봅시다.
:is() 및 :where() 의사 클래스
이 선택자들은 선택자 목록을 그룹화하여 코드를 간결하게 만들어 줍니다.
/* 기존 방식 */
header a:hover,
main a:hover,
footer a:hover {
text-decoration: underline;
}
/* :is() 사용 */
:is(header, main, footer) a:hover {
text-decoration: underline;
}
차이점은 :is()는 내부 선택자의 가장 높은 명시도를 가져오는 반면, :where()는 명시도가 0입니다.
:has() 관계 선택자
부모 선택자를 가능하게 하는 혁신적인 선택자입니다. "특정 요소를 포함하는 요소"를 선택할 수 있습니다.
/* 이미지를 포함하는 단락 */
p:has(img) {
display: flex;
align-items: center;
}
/* 체크된 라디오 버튼을 포함하는 라벨 */
label:has(input[type="radio"]:checked) {
font-weight: bold;
}
/* 자식 요소가 없는 div (빈 컨테이너) */
div:not(:has(*)) {
display: none;
}
:focus-visible 및 :focus-within
/* 키보드 포커스일 때만 스타일 적용 (마우스 클릭은 제외) */
button:focus-visible {
outline: 2px dashed blue;
}
/* 내부 요소 중 하나가 포커스를 받으면 컨테이너 스타일 변경 */
form:focus-within {
background-color: #f5f5f5;
}
실무 CSS 선택자 활용 팁
유지보수성 향상을 위한 팁
- 과도하게 구체적인 선택자 피하기
/* 지양할 것 */
body header nav ul li a.nav-link {}
/* 지향할 것 */
.nav-link {}
- BEM 방법론과 같은 명명 규칙 사용
.block {}
.block__element {}
.block--modifier {}
- 선택자 깊이 제한하기 최대 3단계 이하로 선택자 깊이를 유지하면 유지보수성이 향상됩니다.
성능 최적화 팁
- 선택자는 오른쪽에서 왼쪽으로 해석됨을 이해하기
/* 비효율적: 모든 div를 확인한 후 .container 클래스 확인 */
div.container {}
/* 효율적: .container 클래스를 먼저 찾고 div인지 확인 */
.container {}
- 전체 선택자(*) 사용 제한하기 특히 복잡한 선택자의 일부로 사용할 때 성능에 영향을 줄 수 있습니다.
- ID 선택자 활용하기 ID 선택자는 페이지에서 고유하므로 브라우저가 빠르게 요소를 찾을 수 있습니다.
마무리
CSS 선택자는 단순한 기술 이상의 예술입니다. 기본 선택자부터 시작하여 복잡한 선택자 조합까지 마스터하면, HTML 구조를 변경하지 않고도 원하는 요소에 정확하게 스타일을 적용할 수 있습니다. 이는 유지보수성이 높고 효율적인 CSS 코드를 작성하는 데 필수적입니다.
가장 중요한 것은 실전 경험입니다. 다양한 선택자를 실험해보고, 개발자 도구를 활용하여 선택자가 어떤 요소를 타겟팅하는지 확인해보세요. 점차 더 복잡한 레이아웃과 인터랙션을 구현할 수 있게 될 것입니다.
최신 CSS 선택자는 계속해서 발전하고 있으므로, 새로운 스펙과 브라우저 지원 상황을 주기적으로 확인하는 것도 잊지 마세요. 이제 여러분은 CSS 선택자의 강력함을 활용하여 웹 개발의 새로운 가능성을 탐색할 준비가 되었습니다!
'TypeScript > CSS' 카테고리의 다른 글
CSS 단위 완벽 이해하기: px vs. em vs. rem vs. vh/vw (0) | 2025.05.15 |
---|---|
CSS 애니메이션 마스터하기: 자바스크립트 없이 인터랙티브한 웹 만들기 (0) | 2025.05.13 |
반응형 웹 디자인: 모바일부터 데스크톱까지 완벽하게 대응하는 CSS 기법 (1) | 2025.05.12 |
CSS 변수(Custom Properties)로 코드 유지보수 시간 절반으로 줄이기 (2) | 2025.05.11 |
CSS Grid 완벽 가이드: 복잡한 레이아웃도 쉽게 만드는 방법 (0) | 2025.05.10 |