CSS / Tutorial / 선택자(Selector) / 가상 요소와 가상 클래스 순서

원본 요소에 마우스를 올렸을 때 그 요소의 가상 요소를 바꾸려면 .box:hover::before처럼 원본 요소의 상태를 앞에 쓰고 가상 요소를 선택합니다. 여기서 :hover는 .box의 상태이며 ::before는 그 상태에서 꾸밀 대상입니다. 순서를 바꾸면 상태를 판단하는 대상까지 달라질 수 있으므로 단순히 같은 선택자를 거꾸로 쓴 것으로 생각하면 안 됩니다.

가상 클래스와 가상 요소가 가리키는 대상

가상 클래스(Pseudo-class)는 :hover·:focus-visible·:checked처럼 요소의 상태나 조건을 선택합니다. 가상 요소(Pseudo-element)는 ::before·::after·::first-line처럼 요소와 연결된 특정 부분을 꾸밉니다. ::before와 ::after의 생성 내용은 실제 DOM 자식 요소를 추가한 것과 같지 않습니다.

/* 포인터가 .box 위에 있을 때 .box의 ::before를 꾸밈 */
.box:hover::before {
  color: blue;
}

/* 키보드 포커스가 보이는 .box의 ::before를 꾸밈 */
.box:focus-visible::before {
  color: blue;
}

새 원고에서는 가상 요소에 두 콜론(::), 가상 클래스에 한 콜론(:)을 사용해 구분하는 편이 명확합니다. 오래된 :before·:after 표기는 하위 호환을 위해 여전히 인식되지만, 한 콜론을 썼다고 가상 클래스로 바뀌는 것은 아닙니다. 두 표기를 고치는 것과 선택 대상·순서를 고치는 것은 별개의 문제입니다.

전체 예제: 직접 지정한 색과 상속된 색 비교

아래 코드를 pseudo-order.html로 저장한 뒤 브라우저에서 엽니다. 외부 파일이나 JavaScript는 필요하지 않습니다. 버튼 앞의 마름모는 장식용 생성 내용이며, 버튼의 실제 설명은 HTML 텍스트에 있습니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>가상 요소 선택과 색 상속 비교</title>
  <style>
    body { margin: 20px; font: 16px/1.6 "Noto Sans KR", sans-serif; color: #203348; }
* { box-sizing: border-box; }
    h1 { font-size: 24px; }
    .sample { display: block; width: 100%; max-width: 420px; min-height: 48px;
      margin: 12px 0; padding: 12px; border: 1px solid #8fa5b8;
      background: #f2f6f9; color: #203348; text-align: left; font: inherit; }
    .sample::before { content: "◆"; margin-inline-end: 8px; color: #b13a35; }
    .sample:hover, .sample:focus-visible { color: #1268bd; }
    .sample:focus-visible { outline: 3px solid #bf5c00; outline-offset: 2px; }
    .explicit:hover::before, .explicit:focus-visible::before { color: #1268bd; }
    .inherit::before { color: inherit; }
  </style>
</head>
<body>
<h1>가상 요소의 색 바꾸기</h1>
<button class="sample parent-only" type="button">부모 색만 변경</button>
<button class="sample explicit" type="button">가상 요소도 직접 변경</button>
<button class="sample inherit" type="button">색을 상속</button>
<p>각 버튼에 포인터를 올리거나 Tab으로 이동해 보세요.</p>
</body>
</html>

첫 버튼에 포인터를 올리면 글자는 파란색이 되지만 마름모는 빨간색으로 남습니다. ::before에 직접 빨간색을 지정했기 때문입니다. 두 번째는 :hover::before 규칙으로 마름모도 파란색으로 바꿉니다. 세 번째는 color: inherit로 부모의 색을 따르므로 부모 색 변화가 마름모에도 나타납니다.

Tab으로 버튼에 이동해도 같은 비교가 되도록 :focus-visible 규칙을 함께 넣었습니다. 터치 기기에서는 hover가 없거나 동작이 다를 수 있으므로 중요한 정보나 조작을 hover 하나에만 의존하지 마세요. 이 예제의 버튼은 상태 비교를 위한 요소이며 별도 업무 동작을 수행하지 않습니다.

가상 요소 뒤의 가상 클래스는 무조건 오류인가?

그렇게 일반화할 수는 없습니다. Selectors Level 4 작업 초안은 허용된 조합에서 가상 요소 자체의 상태를 선택하는 문법을 정의합니다. 이 경우 가상 요소 뒤에 붙은 가상 클래스는 원본 요소가 아니라 가상 요소 자체의 상태를 가리키며, 대상 종류와 브라우저 구현 조건을 따로 확인해야 합니다.

예를 들어 .box:hover::before는 .box 영역에 포인터가 있을 때의 ::before를 가리킵니다. .box::before:hover를 사용할 수 있는 조건에서는 ::before 자체가 포인터 대상인 상태를 뜻하므로 같은 의미가 아닙니다. 일반적인 원본 요소 hover 효과를 만들 목적이라면 앞의 방식으로 작성하고, 순서를 뒤집어 같은 효과를 기대하지 마세요. 최신 명세에 있는 문법이 모든 브라우저에서 같은 수준으로 구현되었다는 뜻도 아닙니다.

일부 가상 요소 뒤에는 별도로 정의된 하위 가상 요소를 연결할 수도 있습니다. 예를 들어 list-item으로 만든 ::before의 ::marker는 명세에 정의된 조합이지만, 이를 근거로 임의의 가상 요소를 계속 이어 쓸 수는 없습니다. 따라서 “가상 요소는 반드시 마지막이며 뒤에는 아무것도 올 수 없다”보다 사용하려는 조합의 정의와 지원을 확인하는 설명이 정확합니다.

적용되지 않을 때 확인하는 순서

먼저 원본 요소가 선택되는지와 hover·포커스 조건이 실제로 충족되는지 확인합니다. 다음으로 ::before·::after에 content가 있고 none이나 normal로 덮이지 않았는지 봅니다. 그 뒤 color 같은 상속 속성인지, 가상 요소 자체에 별도 값이 있는지, 더 우선하는 규칙이 있는지 확인하세요.

전체 예제의 첫 번째 마름모 색만 부모에게서 받도록 바꿔 보려면 .parent-only::before에 color: inherit를 추가합니다. 포인터를 올렸을 때 직접 지정 방식과 같은 파란색이 되는지 비교한 뒤 선언을 제거하면 빨간색으로 복원됩니다. 반대로 .explicit:hover::before 규칙을 제거하면 마름모는 처음 지정한 색으로 남습니다.

일반적인 쉼표 구분 선택자 목록에 브라우저가 처리할 수 없는 선택자가 섞이면 규칙 전체가 무시될 수 있습니다. 새롭거나 지원 조건이 다른 선택자는 기존 규칙과 별도로 두고 개발자 도구에서 해석 결과를 확인하는 편이 문제를 찾기 쉽습니다. 가상 요소로만 중요한 문구를 만들지 말고 필수 내용은 실제 HTML에 두세요.

개념과 기본 순서는 MDN 가상 요소 설명, 생성 내용은 ::after 문서, 가상 요소 자체의 상태 선택은 Selectors Level 4의 관련 규칙에서 확인할 수 있습니다.