한 줄 정답 — 콜론 하나(
:)로 시작하면 가상 클래스, 요소의 상태나 위치를 골라낸다(:hover는 마우스 올린 상태,:nth-child(2)는 두 번째 자식). 콜론 둘(::)로 시작하면 가상 요소, DOM에 없는 가짜 부분을 만들어 꾸민다(::before는 요소 앞에 콘텐츠를 끼워 넣음). 가상 클래스는 기존 요소를 선택할 뿐이고, 가상 요소는 새 콘텐츠를 만든다 — 그래서::before/::after는content속성이 없으면 아예 나타나지 않는다.
핵심 요약#
- 콜론 개수가 종류를 가른다.
:하나는 가상 클래스(상태·위치 선택),::둘은 가상 요소(가짜 콘텐츠 생성). - 가상 클래스는
:hover,:focus,:nth-child(),:checked처럼 "지금 어떤 상태/몇 번째인지"로 기존 요소를 고른다. - 가상 요소는
::before,::after,::first-line,::placeholder처럼 요소의 일부나 생성 콘텐츠를 꾸민다.::before/::after는content가 필수다. :nth-child(n)는 형제 전체를 세고 나서 종류를 따진다 — 이 순서를 거꾸로 알면 "왜 안 골라지지"가 생긴다.- 명시도에서 가상 클래스는 클래스급(둘째 칸), 가상 요소는 타입급(셋째 칸)으로 센다.
CSS 가상 클래스와 가상 요소는 콜론 하나·둘로 갈린다#
선택자 뒤에 콜론을 붙이는 두 문법이 있다. 생김새가 비슷해 헷갈리지만, 하는 일이 전혀 다르다. 콜론 하나는 "이미 있는 요소를 어떤 조건으로 고를까"이고, 콜론 둘은 "요소에 없던 부분을 만들어 꾸밀까"이다. 이 한 줄만 잡으면 나머지는 종류 외우기다.
가상 클래스 — 콜론 하나, 요소의 '상태·위치'#
가상 클래스(pseudo-class)는 요소가 특정 상태이거나 특정 위치일 때만 골라낸다. 새 요소를 만들지 않고, 이미 존재하는 요소를 조건부로 선택할 뿐이다.
/* 마우스를 올린 상태 */
a:hover { color: #2563eb; }
/* 키보드·클릭으로 초점이 간 상태 */
input:focus { outline: 2px solid #2563eb; }
/* 체크된 체크박스 */
input:checked { accent-color: #16a34a; }
/* 형제 중 첫 번째 요소 */
li:first-child { font-weight: bold; }
:hover나 :focus처럼 사용자의 동작에 반응하는 것도, :first-child나 :nth-child()처럼 문서 구조상의 위치로 고르는 것도 모두 가상 클래스다. 공통점은 "상자를 새로 만들지 않는다"는 것.
가상 요소 — 콜론 둘, 없던 '가짜 요소'#
가상 요소(pseudo-element)는 HTML에 적지 않은 가상의 부분을 만들어 그곳에 스타일을 준다. 대표가 요소의 앞뒤에 콘텐츠를 끼워 넣는 ::before/::after이고, 텍스트의 첫 줄·첫 글자를 집는 ::first-line/::first-letter, 입력창의 안내 문구를 꾸미는 ::placeholder도 가상 요소다.
/* 요소 맨 앞에 가짜 콘텐츠를 만든다 */
.tag::before {
content: "#";
color: #9ca3af;
}
/* 문단 첫 글자만 키운다 */
p::first-letter {
font-size: 2em;
font-weight: bold;
}
가상 요소는 "없던 것을 만들어 낸다"는 점이 가상 클래스와 결정적으로 다르다. 그래서 ::before/::after는 무엇을 만들지 알려 주는 content 속성이 없으면 화면에 전혀 나타나지 않는다.
한눈 비교#
| 항목 | 가상 클래스 | 가상 요소 |
|---|---|---|
| 콜론 | 하나 : | 둘 :: |
| 하는 일 | 요소의 상태·위치로 선택 | DOM에 없는 부분을 생성·꾸밈 |
| 새 콘텐츠 | 만들지 않음(기존 요소 선택) | 만듦(가상의 부분) |
| 대표 예 | :hover :focus :nth-child() :checked | ::before ::after ::first-line ::placeholder |
content | 필요 없음 | ::before/::after는 필수 |
| 명시도 칸 | 둘째 칸(클래스급) | 셋째 칸(타입급) |
콜론 하나로 쓰는 가상 요소도 있다.
:before,:after,:first-line,:first-letter네 개는 옛 CSS2 문법이라 콜론 하나로도 동작한다(브라우저 호환용). 하지만 의미를 분명히 하려고 지금은 모두::로 쓰는 걸 권장한다.::placeholder,::selection같은 이후에 추가된 가상 요소는 콜론 둘만 받는다.
:hover와 :focus — 상태에 반응하는 가상 클래스#
:hover는 마우스 포인터가 요소 위에 있을 때, :focus는 요소에 초점이 갔을 때(클릭하거나 Tab으로 이동)를 가리킨다. 버튼·링크·입력창의 반응을 만드는 가장 흔한 가상 클래스다.
.btn {
background: #e5e7eb;
transition: background 0.2s;
}
.btn:hover { background: #d1d5db; }
.btn:active { background: #9ca3af; } /* 누르는 순간 */
:hover는 보통 transition과 같이 써서 색이나 크기 변화를 부드럽게 만든다. 상태가 바뀌는 두 값 사이를 잇는 쪽은 transition의 역할이라, 둘을 묶으면 자연스러운 마우스오버 효과가 된다. 자세한 동작은 CSS transition 사용법 글에서 이어 볼 수 있다.
접근성을 위해 :hover에만 의존하지 않는 게 좋다. 키보드 사용자는 마우스를 올릴 수 없으니, 상호작용 요소에는 :focus(또는 :focus-visible)로도 같은 단서를 주는 편이 안전하다.
:nth-child 선택자 — 몇 번째인지로 고르기#
:nth-child(n)는 형제 중 몇 번째 자식인지로 요소를 고른다. 표의 줄무늬, 목록의 홀짝 스타일을 만들 때 자주 쓴다.
li:nth-child(2) { color: #ef4444; } /* 두 번째 */
li:nth-child(odd) { background: #f3f4f6; } /* 홀수 번째 */
li:nth-child(even) { background: #fff; } /* 짝수 번째 */
li:nth-child(3n) { font-weight: bold; } /* 3의 배수 번째 */
odd/even 외에 2n, 3n+1 같은 수식도 받는다. 3n+1은 1, 4, 7…번째를 고른다.
여기서 흔한 함정 하나. :nth-child는 종류를 가리지 않고 형제 전체를 먼저 세고, 그 자리의 요소가 선택자와 맞는지 따진다. 그래서 p:nth-child(2)는 "두 번째 자식이면서 그게 p인 경우"를 뜻한다 — 두 번째 자식이 p가 아니면 아무것도 안 골라진다. "두 번째 p"를 고르고 싶다면 같은 종류만 세는 :nth-of-type(2)를 쓴다.
/* 두 번째 자식이 p일 때만 적용 */
p:nth-child(2) { color: red; }
/* p들 중 두 번째 (종류만 따로 셈) */
p:nth-of-type(2) { color: red; }
::before와 ::after 사용법 — content가 핵심#
::before/::after는 요소의 콘텐츠 맨 앞·맨 뒤에 가상의 콘텐츠를 끼워 넣는다. 아이콘, 라벨, 말머리 기호, 따옴표처럼 HTML을 더럽히지 않고 장식을 넣을 때 쓴다.
핵심은 content 속성이다. 값이 비어도(content: "") 반드시 있어야 가상 요소가 생성된다. 없으면 규칙 자체가 무시된다.
/* 필수 입력 라벨 뒤에 빨간 별 */
.required::after {
content: " *";
color: #ef4444;
}
/* 외부 링크 앞에 화살표 아이콘 */
a[target="_blank"]::before {
content: "↗ ";
color: #6b7280;
}
/* 빈 content로 도형만 만들기 */
.divider::before {
content: "";
display: block;
height: 1px;
background: #e5e7eb;
}
만들어진 가상 요소는 기본적으로 인라인이라, 너비·높이를 주려면 display: block(또는 inline-block)으로 바꾼다. 또 ::before/::after는 <img>, <input> 같은 **대체 요소(replaced element)**에는 붙지 않는다 — 이들은 콘텐츠 영역을 CSS가 채우는 구조가 아니기 때문이다.
content에 넣은 텍스트는 장식용이라 검색·스크린리더가 본문처럼 다루지 않는다. 의미 있는 내용(꼭 읽혀야 할 문구)은 content가 아니라 HTML 본문에 두는 게 맞다.
자주 묻는 질문#
Q. 가상 클래스와 가상 요소 차이가 뭔가요?
A. 콜론 개수와 역할이 다릅니다. 가상 클래스는 콜론 하나(:hover, :nth-child)로 요소의 상태나 위치를 골라 기존 요소를 선택합니다. 가상 요소는 콜론 둘(::before, ::after)로 DOM에 없던 가짜 부분을 만들어 꾸밉니다. 한쪽은 고르기만 하고, 다른 쪽은 새 콘텐츠를 생성한다는 점이 핵심 차이입니다.
Q. 콜론을 하나 쓰나요 둘 쓰나요?
A. 가상 클래스는 항상 콜론 하나, 가상 요소는 콜론 둘이 원칙입니다. 다만 :before, :after, :first-line, :first-letter 네 가지 옛 가상 요소는 호환을 위해 콜론 하나로도 동작합니다. 헷갈리지 않으려면 가상 요소에는 모두 ::를 쓰는 걸 권장합니다.
Q. ::before로 만든 게 화면에 안 보여요.
A. content 속성이 빠졌을 가능성이 가장 큽니다. ::before/::after는 content가 없으면(빈 문자열 content: ""이라도) 생성되지 않습니다. 또 <img>나 <input> 같은 대체 요소에는 붙지 않고, 도형을 만들려 했다면 인라인 기본값이라 display: block으로 바꿔야 너비·높이가 먹습니다.
Q. :nth-child가 원하는 요소를 못 고를 때는요?
A. :nth-child는 종류를 가리지 않고 형제 전체를 센 다음, 그 자리 요소가 선택자와 맞는지 확인합니다. 그래서 p:nth-child(2)는 "두 번째 자식이 마침 p일 때"만 적용됩니다. "p들 중 두 번째"를 원하면 같은 종류만 세는 :nth-of-type(2)를 쓰세요.
관련 글#
가상 클래스·가상 요소는 선택자 문법의 일부다. 결합 선택자까지 묶어 보거나, :hover를 실제 움직임으로 잇는 글로 넘어가면 좋다.
- 결합 선택자와 가상 클래스 — 자식·자손·형제 그리고 :hover, :nth-child — 같은 주제를 강의 흐름에서 결합 선택자와 함께 더 깊이
- CSS transition 사용법 — hover 애니메이션 부드럽게 만들기 —
:hover상태 변화를 부드럽게 잇는 다음 단계 - CSS 선택자 우선순위 — 점수 계산법과 CSS가 안 먹을 때 — 가상 클래스·가상 요소가 명시도에서 어느 칸에 들어가는지