CSS 독학 가이드 8 - 고급 선택자와 가상 요소

learning by Seven Fingers Studio 21분
CSS선택자가상요소가상클래스beforeafter

선택자를 제대로 쓰면 HTML이 깔끔해진다

HTML에 class를 너무 많이 붙이면 코드가 지저분해지죠. 고급 선택자를 잘 쓰면 HTML은 간결하게 유지하면서도 원하는 요소를 정확히 선택할 수 있어요.

저는 처음엔 모든 요소에 class를 붙였는데, 선택자를 제대로 배우고 나니 class가 절반으로 줄었습니다!

자손 선택자 vs 자식 선택자

자손 선택자 (Descendant Selector)

모든 자손을 선택해요. (자식, 손자, 증손자 모두)

div p {
  color: red;
}
<div>
  <p>빨강</p>  <!-- 선택됨 -->
  <section>
    <p>빨강</p>  <!-- 선택됨 (손자도 포함) -->
  </section>
</div>

자식 선택자 (Child Selector)

직접적인 자식만 선택해요.

div > p {
  color: red;
}
<div>
  <p>빨강</p>  <!-- 선택됨 -->
  <section>
    <p>검정</p>  <!-- 선택 안 됨 (손자는 제외) -->
  </section>
</div>

언제 뭘 쓸까?

  • 일반적: 자손 선택자 ( )
  • 정확히 1단계만: 자식 선택자 (>)

인접 선택자

인접 형제 선택자 (+)

바로 다음 형제만 선택해요.

h2 + p {
  font-size: 1.2rem;
  color: #666;
}
<h2>제목</h2>
<p>이 문단만 선택됨</p>
<p>이건 선택 안 됨</p>

용도: 제목 바로 다음 첫 문단을 강조할 때!

일반 형제 선택자 (~)

뒤따르는 모든 형제를 선택해요.

h2 ~ p {
  color: blue;
}
<h2>제목</h2>
<p>파랑</p>
<div>...</div>
<p>파랑</p>  <!-- h2 뒤의 모든 p -->

속성 선택자

HTML 속성 값으로 선택해요.

기본 속성 선택

/* href 속성이 있는 a 태그 */
a[href] {
  color: blue;
}

/* target="_blank"인 링크 */
a[target="_blank"] {
  color: red;
}

/* type="text"인 input */
input[type="text"] {
  border: 1px solid #ccc;
}

부분 매칭

/* href가 "https"로 시작 */
a[href^="https"] {
  color: green;
}

/* href가 ".pdf"로 끝 */
a[href$=".pdf"] {
  color: red;
}

/* href에 "google"이 포함 */
a[href*="google"] {
  color: blue;
}

/* class에 "btn"이라는 단어 포함 */
[class~="btn"] {
  padding: 10px;
}

실전 예제:

/* 외부 링크에 아이콘 추가 */
a[href^="http"]::after {
  content: " 🔗";
}

/* PDF 링크 스타일 */
a[href$=".pdf"]::before {
  content: "📄 ";
}

가상 클래스 (Pseudo-classes)

특정 상태의 요소를 선택해요.

사용자 동작

/* 마우스 올렸을 때 */
a:hover {
  color: red;
}

/* 클릭 중일 때 */
button:active {
  transform: scale(0.95);
}

/* 포커스 되었을 때 */
input:focus {
  border-color: blue;
  outline: none;
}

/* 방문한 링크 */
a:visited {
  color: purple;
}

구조 가상 클래스

/* 첫 번째 자식 */
li:first-child {
  font-weight: bold;
}

/* 마지막 자식 */
li:last-child {
  border-bottom: none;
}

/* n번째 자식 */
li:nth-child(2) {
  color: red;
}

/* 홀수 번째 */
li:nth-child(odd) {
  background: #f0f0f0;
}

/* 짝수 번째 */
li:nth-child(even) {
  background: #ffffff;
}

/* 3의 배수 */
li:nth-child(3n) {
  color: blue;
}

/* 3n+1 (1, 4, 7, 10...) */
li:nth-child(3n+1) {
  font-weight: bold;
}

/* 뒤에서 첫 번째 */
li:nth-last-child(1) {
  margin-bottom: 0;
}

실행 결과 (홀수/짝수 예제):

  • 1. 첫 번째 항목 (홀수, 굵게)
  • 2. 두 번째 항목 (짝수, 빨강)
  • 3. 세 번째 항목 (홀수, 3의 배수)
  • 4. 네 번째 항목 (짝수, 3n+1 굵게)
  • 5. 마지막 항목 (홀수, 하단 여백 없음)

실전 예제: 테이블 줄무늬

tr:nth-child(odd) {
  background: #f9f9f9;
}

tr:nth-child(even) {
  background: #ffffff;
}

타입 가상 클래스

/* 같은 타입의 첫 번째 */
p:first-of-type {
  font-size: 1.2rem;
}

/* 같은 타입의 마지막 */
p:last-of-type {
  margin-bottom: 0;
}

/* 같은 타입의 n번째 */
p:nth-of-type(2) {
  color: blue;
}

상태 가상 클래스

/* 체크된 체크박스/라디오 */
input:checked {
  accent-color: blue;
}

input:checked + label {
  font-weight: bold;
}

/* 비활성화 */
button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* 필수 입력 */
input:required {
  border-color: red;
}

/* 선택 가능 (optional) */
input:optional {
  border-color: gray;
}

/* 유효한 입력 */
input:valid {
  border-color: green;
}

/* 유효하지 않은 입력 */
input:invalid {
  border-color: red;
}

가상 요소 (Pseudo-elements)

실제로 존재하지 않는 요소를 만들어요. :: 두 개 콜론 사용!

::before와 ::after

요소의 앞/뒤에 콘텐츠를 추가해요.

.quote::before {
  content: '"';
  font-size: 2rem;
  color: #ccc;
}

.quote::after {
  content: '"';
  font-size: 2rem;
  color: #ccc;
}

content 속성 필수! 없으면 표시 안 돼요.

실전 예제 1: 아이콘 추가

.new-badge::after {
  content: "NEW";
  background: red;
  color: white;
  padding: 2px 5px;
  font-size: 0.7rem;
  margin-left: 5px;
  border-radius: 3px;
}

실전 예제 2: 말풍선 꼬리

.tooltip {
  position: relative;
  background: #333;
  color: white;
  padding: 10px;
  border-radius: 5px;
}

.tooltip::after {
  content: "";
  position: absolute;
  bottom: -10px;
  left: 50%;
  transform: translateX(-50%);
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-top: 10px solid #333;
}

실전 예제 3: 클리어픽스

.clearfix::after {
  content: "";
  display: table;
  clear: both;
}

float 레이아웃에서 필수였던 기술이에요! (지금은 Flexbox 씀)

::first-letter (첫 글자)

p::first-letter {
  font-size: 3rem;
  font-weight: bold;
  float: left;
  margin-right: 10px;
  line-height: 1;
}

신문 스타일 첫 글자 장식!

::first-line (첫 줄)

p::first-line {
  font-weight: bold;
  color: #333;
}

::selection (선택 영역)

::selection {
  background: #ffeb3b;
  color: #000;
}

텍스트 드래그했을 때 색상 변경!

:not() 가상 클래스

특정 조건을 제외해요.

/* 마지막 아이템 빼고 모두 margin-bottom */
li:not(:last-child) {
  margin-bottom: 10px;
}

/* disabled 아닌 버튼 */
button:not(:disabled) {
  cursor: pointer;
}

/* .active 클래스 없는 링크 */
a:not(.active) {
  color: gray;
}

/* 여러 조건 */
p:not(.intro):not(.outro) {
  color: #666;
}

:is() 가상 클래스 (최신)

여러 선택자를 묶어요.

/* 이전 방식 */
h1 span,
h2 span,
h3 span {
  color: red;
}

/* :is() 사용 */
:is(h1, h2, h3) span {
  color: red;
}

복잡한 선택자 간소화:

/* 이전 */
.header a:hover,
.sidebar a:hover,
.footer a:hover {
  color: blue;
}

/* :is() 사용 */
:is(.header, .sidebar, .footer) a:hover {
  color: blue;
}

:has() 가상 클래스 (부모 선택!)

특정 자식을 가진 부모를 선택해요. CSS의 혁명!

/* img를 포함한 article */
article:has(img) {
  display: grid;
  grid-template-columns: 2fr 1fr;
}

/* 체크된 input을 가진 label */
label:has(input:checked) {
  font-weight: bold;
}

/* 자식이 없는 div */
div:not(:has(*)) {
  display: none;
}

실전 예제: 동적 레이아웃

/* 이미지가 있으면 2열, 없으면 1열 */
.card {
  display: grid;
}

.card:has(img) {
  grid-template-columns: 200px 1fr;
}

2023년부터 주요 브라우저 지원!

실전 조합 예제

외부 링크에 자동 아이콘

a[href^="http"]:not([href*="mysite.com"])::after {
  content: " ↗";
  font-size: 0.8em;
  color: #999;
}

폼 검증 표시

input:required:valid {
  border-color: green;
}

input:required:invalid:not(:placeholder-shown) {
  border-color: red;
}

input:required:valid::after {
  content: "✓";
  color: green;
}

카드 호버 효과

.card {
  position: relative;
  transition: transform 0.3s;
}

.card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0,0,0,0.1);
  opacity: 0;
  transition: opacity 0.3s;
}

.card:hover {
  transform: translateY(-5px);
}

.card:hover::before {
  opacity: 1;
}

선택자 우선순위 복습

  1. !important (비추천)
  2. 인라인 스타일 (1000점)
  3. ID 선택자 (100점)
  4. 클래스, 속성, 가상 클래스 (10점)
  5. 태그, 가상 요소 (1점)
  6. 전체 선택자 (0점)
#header .nav li a:hover { }
/* 100 + 10 + 1 + 1 + 10 = 122점 */

.nav a:hover { }
/* 10 + 1 + 10 = 21점 */

점수가 높은 게 우선 적용!

다음 단계

고급 선택자를 마스터했으니 이제 CSS 변수와 최신 기능을 배울 차례예요. 코드 재사용성과 유지보수성을 극대화할 수 있습니다!

다음 글에서는 CSS 변수(Custom Properties), calc(), clamp() 등 최신 CSS 기능을 다룰 거예요.

실습 과제:

  • nth-child로 테이블 줄무늬 만들기
  • ::before와 ::after로 말풍선 만들기
  • :has()로 이미지 유무에 따라 레이아웃 바꾸기
  • 외부 링크에 자동 아이콘 추가하기

선택자를 잘 쓰면 HTML에 불필요한 class가 줄어들어요. 깔끔한 코드의 시작!

시리즈 네비게이션

← 블로그 목록으로