변수로 값을 관리하게 되면서, 나는 그 값을 언제 어떤 요소에 적용할지를 더 정교하게 겨냥하고 싶어졌다. 마우스를 올렸을 때만, 짝수 번째 항목에만, 혹은 요소에 없는 가상의 조각에만 스타일을 주고 싶었다. 이런 세밀한 겨냥을 가능하게 하는 것이 가상 클래스와 가상 요소였다. 마크업을 전혀 건드리지 않고도 상태와 위치와 없던 요소까지 스타일링할 수 있었다.


이번 편은 요소의 상태와 위치를 겨냥하는 가상 클래스와, 없던 조각을 만들어내는 가상 요소를 다룬다. 마우스와 포커스 같은 상태를 읽는 법, 순서와 위치로 요소를 고르는 법, 선택자를 간결하게 묶는 함수형 가상 클래스, 그리고 요소 앞뒤에 콘텐츠를 더하는 가상 요소까지 살펴본다. 이것들을 익히면 마크업을 늘리지 않고도 훨씬 풍부한 스타일을 짤 수 있다.

1. 상태를 읽는 가상 클래스

가상 클래스는 요소가 처한 특정 상태나 조건을 겨냥하는 선택자다. 이름 앞에 콜론을 붙여 표현하는데, 마크업에 실제로 존재하는 클래스가 아니라 브라우저가 상황에 따라 판단하는 가상의 상태를 가리킨다. 나는 이 가상 클래스 덕분에, 상황에 반응하는 스타일을 자바스크립트 없이 순수하게 스타일만으로 만들 수 있다는 게 늘 놀랍다.


가장 익숙한 상태 가상 클래스는 마우스를 올린 상태를 겨냥하는 것이다. 마우스가 요소 위에 있을 때만 스타일을 적용해, 버튼이나 링크가 반응하도록 만든다. 나는 거의 모든 상호작용 요소에 이 상태를 걸어, 마우스를 올리면 색이나 그림자가 변하게 한다. 사용자에게 이 요소가 누를 수 있는 것임을 알리는 가장 기본적인 피드백이다.


포커스 상태를 겨냥하는 가상 클래스도 중요하다. 키보드로 이동하거나 입력란을 눌러 포커스가 들어온 요소에 스타일을 준다. 나는 포커스 표시를 특히 신경 쓰는데, 키보드만으로 화면을 쓰는 사용자에게 지금 어디에 있는지를 알려주는 유일한 단서이기 때문이다. 포커스 스타일을 지워버리면 키보드 사용자는 길을 잃는다.


눌리는 순간의 상태, 비활성화된 상태, 선택된 상태를 겨냥하는 가상 클래스도 있다. 버튼이 눌리는 찰나에 반응하거나, 사용할 수 없는 요소를 흐리게 표시하거나, 체크된 항목을 강조하는 데 쓴다. 나는 이런 상태들을 조합해, 요소가 지금 어떤 상황인지를 시각적으로 명확히 드러낸다. 상태마다 다른 모습을 주면 사용자가 화면을 직관적으로 이해한다.


선택된 상태를 읽는 가상 클래스는 특히 흥미로운 표현을 열어준다. 체크박스가 선택되었는지에 따라 주변 요소의 스타일을 바꾸는 식이다. 나는 이를 활용해 자바스크립트 없이 켜고 끄는 토글이나 접고 펴는 요소를 만들기도 한다. 상태 가상 클래스와 형제를 겨냥하는 선택자를 엮으면, 순수 스타일만으로 제법 동적인 동작을 구현할 수 있다.


상태 가상 클래스는 앞서 다룬 트랜지션과 짝을 이룰 때 빛난다. 상태가 바뀔 때 값의 차이를 정의하는 것이 가상 클래스라면, 그 변화를 부드럽게 잇는 것이 트랜지션이다. 나는 마우스를 올린 상태에 트랜지션을 걸어, 색과 모양이 부드럽게 변하도록 한다. 두 기능이 만나면 상호작용이 딱딱하지 않고 자연스럽게 반응한다.

2. 순서와 위치로 겨냥하기

가상 클래스에는 요소의 구조적 위치를 겨냥하는 것들도 있다. 형제들 중 몇 번째인지, 첫 번째인지 마지막인지, 유일한 자식인지 같은 조건으로 요소를 고른다. 나는 이 구조 가상 클래스 덕분에, 마크업에 별도의 표시를 달지 않고도 특정 위치의 요소만 겨냥할 수 있다. 위치라는 정보 자체를 선택의 기준으로 삼는 것이다.


순서로 겨냥하는 가상 클래스는 특히 유용하다. 몇 번째 요소인지를 지정해 겨냥할 수 있는데, 짝수 번째나 홀수 번째, 혹은 일정한 간격의 요소들을 한꺼번에 고를 수도 있다. 나는 표의 짝수 행에만 옅은 배경을 줘서 줄무늬를 만들 때 이 방식을 쓴다. 행마다 클래스를 달지 않고도, 순서만으로 규칙적인 무늬를 입히는 것이다.


첫 번째와 마지막 요소를 겨냥하는 가상 클래스도 자주 쓴다. 목록의 마지막 항목에서만 구분선을 없애거나, 첫 항목의 위쪽 여백을 제거하는 식이다. 나는 항목들 사이에 구분선을 넣되 마지막 항목 뒤에는 넣지 않으려 할 때, 마지막을 겨냥하는 가상 클래스를 쓴다. 경계의 요소를 특별히 다루는 이런 처리가 목록을 깔끔하게 마무리한다.


순서를 세는 방식에는 미묘한 구분이 있다. 종류를 가리지 않고 형제 중 몇 번째인지를 세는 것과, 같은 종류의 요소 중에서만 몇 번째인지를 세는 것이 다르다. 나는 이 차이를 모르고 엉뚱한 요소가 선택되어 한참 헤맨 적이 있다. 서로 다른 종류의 요소가 섞여 있을 때 이 구분이 결과를 크게 바꾸므로, 무엇을 기준으로 세는지 정확히 알아야 한다.


구조 가상 클래스는 마크업과 스타일을 느슨하게 연결한다. 위치에 따라 스타일이 결정되므로, 요소의 순서가 바뀌면 스타일도 자동으로 따라온다. 나는 이 자동성이 편리하면서도 가끔 함정이 된다고 느낀다. 요소를 추가하거나 순서를 바꾸면 의도치 않게 스타일이 옮겨갈 수 있기 때문이다. 위치 기반 겨냥은 강력한 만큼 구조 변화에 민감하다.


그래서 나는 구조 가상 클래스를 규칙적인 반복이 명확한 곳에 주로 쓴다. 줄무늬나 경계 처리처럼 위치와 스타일의 관계가 분명한 경우다. 반대로 특정 요소 하나만 다르게 하고 싶을 때는, 위치로 겨냥하기보다 그 요소에 직접 클래스를 다는 편이 안전하다. 도구의 성격을 알고 상황에 맞게 고르면 구조 변화에도 흔들리지 않는다.

3. 선택자를 간결하게 묶기

선택자를 짜다 보면 비슷한 규칙을 여러 번 반복하게 될 때가 있다. 이럴 때 여러 선택자를 하나로 묶어주는 함수형 가상 클래스가 유용하다. 여러 대상을 괄호 안에 나열해 한 번에 겨냥하면, 길게 늘어지던 선택자를 간결하게 줄일 수 있다. 나는 이 묶음 기능으로 반복되던 긴 선택자들을 깔끔하게 정리했다.


여러 조건을 묶는 가상 클래스는 선택자의 중복을 없앤다. 여러 조상 아래의 같은 요소를 겨냥할 때, 조상마다 규칙을 반복하는 대신 조상들을 괄호로 묶어 한 번에 적는다. 나는 여러 종류의 컨테이너 안에 있는 같은 요소에 같은 스타일을 줄 때 이 방식을 쓴다. 규칙이 짧아지면 읽기도 쉽고 고치기도 편해진다.


특정 조건을 제외하는 가상 클래스도 있다. 어떤 대상을 겨냥하되 그중 특정한 것만 빼고 싶을 때 쓴다. 나는 목록의 모든 항목에 구분선을 주되 마지막만 빼는 처리를, 마지막을 제외한다는 조건으로 간결하게 표현한다. 무언가를 골라낸 뒤 일부를 덜어내는 이 방식은, 예외를 다루는 선택자를 훨씬 자연스럽게 만든다.


이 함수형 가상 클래스들은 우선순위에 미묘한 영향을 준다. 어떤 것은 괄호 안에서 가장 강한 대상의 우선순위를 따르고, 어떤 것은 우선순위를 전혀 더하지 않는다. 나는 남이 쉽게 덮어쓸 수 있어야 하는 기본 스타일에는 우선순위를 더하지 않는 쪽을 쓴다. 선택자를 묶으면서 동시에 우선순위까지 조절할 수 있다는 점이 이 도구의 숨은 강점이다.


우선순위를 더하지 않는 가상 클래스는 특히 유용하다. 기본 스타일을 이걸로 감싸두면, 나중에 그 위를 덮어쓸 때 우선순위 다툼을 걱정하지 않아도 된다. 나는 공용 컴포넌트의 기본값을 이렇게 약하게 깔아두고, 쓰는 쪽에서 가볍게 변형을 얹게 한다. 강한 선택자로 못 박기보다, 일부러 힘을 빼서 유연함을 남기는 것이다.


이런 묶음 도구들은 선택자를 짧고 명료하게 유지하는 데 크게 기여한다. 나는 선택자가 길고 복잡해질 것 같으면, 이 함수형 가상 클래스로 묶을 수 없는지 먼저 생각한다. 반복을 줄이고 우선순위를 통제하는 이 도구들은, 규모가 큰 스타일을 관리 가능한 상태로 유지하는 데 든든한 조력자가 된다.

4. 없던 조각을 만드는 가상 요소

가상 요소는 가상 클래스와 이름은 비슷하지만 하는 일이 다르다. 가상 클래스가 이미 있는 요소의 상태나 위치를 겨냥한다면, 가상 요소는 마크업에 없던 새로운 조각을 만들어낸다. 나는 이 가상 요소로, 마크업을 늘리지 않고도 장식이나 아이콘 같은 요소를 요소의 앞뒤에 붙일 수 있다는 게 늘 유용하다고 느낀다.


가장 많이 쓰는 가상 요소는 요소의 내용 앞과 뒤에 조각을 더하는 것이다. 이 조각에 내용을 지정하면, 실제 마크업에는 없지만 화면에는 보이는 요소가 생긴다. 나는 필수 입력 항목의 라벨 뒤에 강조 표시를 붙이거나, 외부 링크 뒤에 작은 아이콘을 더할 때 이 방식을 쓴다. 순수하게 장식적인 요소를 마크업에 섞지 않아도 되는 것이다.


가상 요소를 쓸 때는 내용을 반드시 지정해야 한다. 내용이 비어 있더라도 비어 있음을 명시해야 요소가 생성된다. 나는 처음에 이 규칙을 몰라 가상 요소가 아예 나타나지 않아 당황했다. 내용을 지정하는 것이 가상 요소를 존재하게 하는 스위치라는 걸 알고 나서는, 빈 조각을 만들어 순수한 장식 도형으로 쓰는 법도 익혔다.


가상 요소는 도형이나 배경으로도 활용된다. 빈 내용의 조각을 만들어 크기와 배경을 주면, 작은 삼각형이나 원 같은 장식을 그릴 수 있다. 나는 말풍선의 꼬리나, 요소를 꾸미는 작은 도형을 이 방식으로 만든다. 이미지 파일을 따로 두지 않고 스타일만으로 도형을 그리니, 관리할 자원이 줄고 색도 자유롭게 바꿀 수 있다.


가상 요소에 담는 내용은 신중해야 한다. 이 내용은 장식이지 실제 정보가 아니므로, 중요한 내용을 여기에 담으면 안 된다. 화면 낭독기가 이 내용을 제대로 읽지 못할 수 있기 때문이다. 나는 가상 요소를 순수하게 시각적인 장식에만 쓰고, 의미 있는 정보는 반드시 실제 마크업에 둔다. 보기 좋게 꾸미는 것과 정보를 전달하는 것을 분리하는 원칙이다.


가상 요소와 계수 기능을 엮으면 자동 번호 매기기 같은 것도 만들 수 있다. 요소들에 순서대로 번호를 세어 가상 요소로 표시하는 것이다. 나는 목차나 단계별 안내에서 번호를 마크업에 직접 쓰지 않고 이 방식으로 자동 생성한다. 항목이 추가되거나 순서가 바뀌어도 번호가 알아서 다시 매겨지니, 손으로 번호를 관리하는 수고가 사라진다.

5. 포커스와 접근성, 그리고 절제

가상 클래스 중에서 접근성과 가장 밀접한 것이 포커스 관련 클래스다. 그중에서도 키보드로 이동했을 때만 포커스 표시를 보여주는 가상 클래스가 특히 중요하다. 마우스로 눌렀을 때는 표시를 감추고, 키보드로 이동했을 때만 또렷한 표시를 주는 것이다. 나는 이 구분 덕분에 접근성과 미관을 동시에 만족시킬 수 있게 되었다.


예전에는 마우스로 버튼을 눌러도 포커스 표시가 남아 지저분해 보인다는 이유로, 포커스 표시를 아예 지워버리는 잘못된 관행이 있었다. 그런데 이러면 키보드 사용자가 자기 위치를 알 수 없게 된다. 나는 키보드 조작일 때만 표시하는 가상 클래스를 알고 나서, 표시를 지우는 대신 상황에 따라 조절하는 올바른 방법을 쓰게 되었다.


포커스 표시는 지우는 것이 아니라 다듬는 것이다. 기본 표시가 마음에 들지 않으면 없애는 대신, 사이트 디자인에 어울리는 또렷한 표시로 바꾸면 된다. 나는 포커스 표시를 브랜드 색으로 칠하고 적당한 여백을 줘서, 눈에 잘 띄면서도 디자인과 조화되게 만든다. 포커스 표시는 접근성의 생명줄이므로 없애는 선택지는 없다.


가상 클래스와 가상 요소는 강력하지만 남용하면 스타일을 이해하기 어렵게 만든다. 순서로 겨냥한 스타일이 너무 많으면 마크업만 봐서는 왜 그 요소가 그렇게 보이는지 알 수 없다. 나는 이런 도구를 쓸 때, 나중에 다른 사람이나 미래의 내가 이해할 수 있을지를 생각한다. 영리한 선택자보다 읽기 쉬운 선택자가 대개 더 낫다.


특히 가상 요소로 만든 콘텐츠는 화면 낭독기가 놓칠 수 있다는 점을 늘 기억한다. 나는 정보를 전달하는 내용은 절대 가상 요소에만 의존하지 않고, 반드시 실제 마크업에도 둔다. 가상 요소는 어디까지나 시각적인 보조 수단이다. 보이는 것과 전달되는 것이 어긋나지 않도록 하는 것이, 이 도구들을 책임감 있게 쓰는 길이다.


정리하면 가상 클래스는 요소의 상태와 위치를 겨냥하고, 함수형 가상 클래스는 선택자를 묶고 우선순위를 조절하며, 가상 요소는 마크업에 없던 조각을 만들어 장식한다. 나는 이 도구들로 마크업을 늘리지 않고도 풍부한 스타일을 짜되, 접근성과 가독성을 함께 지키려 한다. 다음 편에서는 요소들이 앞뒤로 겹치는 순서를 결정하는 z-index와 쌓임 맥락을 다룬다.