서론: CSS가 이벤트를 처리하는 시대
JavaScript 이벤트 리스너를 쓰지 않고도 UI 상태 변화에 반응하고 싶다면? CSS 의사 클래스(Pseudo-class)가 그 해답이 될 수 있어요. 최근 CSS는 점점 더 많은 상태를 추적하고, JavaScript 이벤트와 유사한 동작을 제공하고 있습니다. 이 글에서는 실제로 자주 쓰이는 의사 클래스들을 JavaScript 이벤트와 대조하며 살펴보고, 곧 도래할 event-trigger 기능까지 소개합니다.
이 글은 원문(근거자료)을 기반으로, 한국 개발자 실무 맥락에 맞게 재구성했어요.
본론 1: 상태 기반 의사 클래스와 JavaScript 이벤트 비교
:hover, :active – 기본 중의 기본
:hover는 마우스가 요소에 들어오고 나가는 동안의 상태를 나타내요. JavaScript의 pointerenter/pointerleave 이벤트와 유사하죠. :active는 마우스, 터치, 스타일러스로 요소를 누르는 동안 매칭되는데, pointerdown과 pointerup/pointercancel 사이의 상태와 같아요.
:focus와 :focus-visible
:focus는 JavaScript의 focus 이벤트와 대응되고, :focus-visible은 키보드 사용 여부 등 브라우저 휴리스틱에 따라 포커스 표시가 필요할 때만 매칭됩니다. JavaScript에서도 이 상태를 확인하고 싶다면 matches()를 쓸 수 있어요.
element.addEventListener('focus', (event) => {
if (event.target.matches(':focus-visible')) {
// 키보드 포커스 표시를 위한 스타일 적용
}
});
:focus-within과 :has() – 부모에서 자식 상태 감지
이 두 의사 클래스는 자식 요소의 상태를 부모에서 감지할 수 있게 해줘요. 폼 전체가 포커스를 받았을 때 스타일을 주고 싶다면 아래 두 선택자는 동일하게 동작합니다.
form:focus-within {
/* 내부 요소가 포커스되면 폼 강조 */
}
form:has(:focus) {
/* 위와 동일한 효과 */
}
:checked와 폼 검증 의사 클래스
:checked는 체크박스나 라디오 버튼의 선택 상태를 나타내며, JavaScript change 이벤트와 유사해요. 폼 검증 관련해서는 :valid, :invalid, :user-valid, :user-invalid가 있는데, 특히 :user-valid는 사용자가 값을 입력하고 포커스를 잃은 후에만 매칭되어 좀 더 사용자 친화적인 UX를 제공합니다.
input.addEventListener('input', () => {
if (input.validity.valid) {
// 유효한 입력
} else {
// 유효하지 않은 입력 (invalid 이벤트는 발생하지 않음)
}
});
미디어 요소 의사 클래스 – 영상/오디오 상태 스타일링
인터랙티브 미디어 요소의 재생 상태를 CSS로 스타일링할 수 있는 새로운 의사 클래스들이 등장했어요. Chrome은 아직 미지원이지만 Firefox에서 지원하고, Interop 2026에 포함되어 있어요.
| 의사 클래스 | JavaScript 이벤트 |
|---|---|
:buffering | waiting |
:muted | volumechange |
:paused | pause |
:playing | playing |
:seeking | seeking |
:stalled | stalled |
:volume-locked | N/A |
video:playing {
/* 재생 중일 때 테두리 강조 */
outline: 2px solid green;
}
:popover-open, :open, :modal 등
팝오버, 다이얼로그, details 요소의 열림 상태를 나타내는 의사 클래스도 있어요. JavaScript에서는 toggle 이벤트를 수신하고 open 프로퍼티를 확인해야 하지만, CSS는 바로 상태를 알 수 있죠.
본론 2: event-trigger – CSS로 이벤트 듣는 미래 기능
개념과 기본 문법
Chrome의 스크롤 트리거 애니메이션 구현에서 등장한 event-trigger는 아직 어떤 브라우저에서도 지원되지 않는 실험적인 기능이에요. 하지만 가능성을 미리 살펴보면, CSS에서 실제 이벤트를 듣고 애니메이션을 트리거할 수 있게 됩니다.
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
button {
/* 클릭하면 --event 트리거 발생 */
event-trigger: --event click;
}
div {
/* --event 발생 시 애니메이션 재생 */
animation-trigger: --event play-forwards;
animation: fade-in 300ms both;
}
상태 유지 이벤트 트리거
관심(interest) 진입/이탈 같은 상태 기반 이벤트는 슬래시(/)로 구분하고, 각 상태에 맞는 애니메이션 액션을 지정해요.
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
button {
/* 관심 진입 / 이탈 */
event-trigger: --event interest / interest;
}
div {
/* 진입 시 정방향, 이탈 시 역방향 재생 */
animation-trigger: --event play-forwards play-backwards;
animation: fade-in 300ms both;
}
가능한 애니메이션 액션은 none, play, play-once, play-forwards, play-backwards, pause, reset, replay 등이 있어요.
주의사항 및 한계
- 아직 미지원 기능이므로 프로덕션에서 사용하면 안 되고, 브라우저 지원 현황을 반드시 확인해야 해요.
event-trigger가 모든 이벤트를 대체할 수는 없어요. 특히 복잡한 상태 로직은 여전히 JavaScript가 필요해요.- CSS 의사 클래스는 상태를 나타내는 것이지 이벤트가 아니므로, 이벤트 발생 순서에 의존하는 로직은 JavaScript로 구현하는 게 안전해요.
결론: 실무 적용 조언
CSS 의사 클래스를 잘 활용하면 JavaScript 코드를 줄이고 성능을 향상시킬 수 있어요. 특히 폼 검증, 포커스 관리, 미디어 요소 상태 표시 등에서 강력한 도구가 됩니다. 다만, 아직 실험적인 기능들은 신중하게 접근하고, 폴리필이나 대체 수단을 고려하세요.
다음 단계로는 CSS 애니메이션 트리거의 명세 변화를 주시하고, 브라우저 호환성을 확인해 보세요. 실제 프로젝트에 적용하기 전에 작은 컴포넌트부터 테스트해 보는 것을 추천해요.
함께 보면 좋은 글:
