"Selector 는 CSS 가 DOM 한테 묻는 질문이야. '이 모양 매치하는 모든 element 보여줘.' 답이 규칙이 적용되는 집합."
다섯 카테고리
모든 CSS selector 는 다섯 카테고리 중 하나 (또는 조합) 에 속해:
- Simple selector — element type, class, ID, attribute 또는 전체로 매치.
- Pseudo-class — 상태 (
:hover,:checked) 나 위치 (:nth-child) 로 매치. - Pseudo-element — element 의 개념적 부분 매치 (
::before,::placeholder). - Combinator — element 간 관계 묘사 (공백,
>,+,~). - Logical combinator — 로직으로 selector 그룹화 (
:is(),:where(),:not(),:has()).
Simple Selector
h1— 모든<h1>element. (type selector).btn— class 가btn인 모든 element. (class selector)#main— id 가main인 element. (id selector — ID 는 페이지마다 유니크해야 함)*— 모든 element. (universal selector — 리셋에 유용, selector 매칭에서 비쌈)[type="email"]— 매치하는 attribute 가진 모든 element. (attribute selector)
Attribute selector: 작은 query language
Attribute selector 가 보기보다 강력해:
[disabled]— attribute 있음 (어떤 값이든).[type="email"]— 정확 매치.[class~="primary"]— class 리스트가 단어 전체 포함.[lang|="en"]— 값이 정확히en또는en-으로 시작.[href^="https"]— 시작.[href$=".pdf"]— 끝.[href*="github.com"]— 포함.[data-state="open" i]—i플래그가 case-insensitive 로.
이게 class 이름 하나도 없이 외부 링크를 내부 링크와 다르게 스타일링하거나 모든 PDF 링크에 아이콘 더하는 방법.
Pseudo-class: 상태와 위치
상태 pseudo-class 가 element 의 현재 상태 매치:
- 사용자 action:
:hover,:focus,:focus-visible,:focus-within,:active - 폼 상태:
:checked,:disabled,:required,:optional,:valid,:invalid,:placeholder-shown - 링크 상태:
:link,:visited - 콘텐츠 상태:
:empty(자식 없음),:target(URL fragment) - 부정:
:not(selector)— 안쪽 selector 가 매치 안 하는 element 매치
위치 pseudo-class 가 형제 중 위치로 매치:
:first-child,:last-child,:only-child:nth-child(n),:nth-child(odd),:nth-child(2n+1),:nth-child(3n):first-of-type,:nth-of-type(n)— 위와 같지만 같은 element type 형제만 세기
Pseudo-element: 진짜 element 아닌 부분 스타일링
Pseudo-element 가 element 의 개념적 부분 스타일링. 이중 콜론 주의:
::before,::after— 생성된 콘텐츠. 항상content: "..."와 페어. 아이콘, 장식 인용, 리스트 마커, 배지에.::placeholder— input placeholder 텍스트 스타일링.::selection— 사용자가 선택했을 때 하이라이트된 텍스트.::first-letter,::first-line— 타이포그래피 효과.::marker— 리스트 항목의 bullet/숫자.::backdrop—showModal()로 연<dialog>뒤 어두운 overlay.
Combinator: element 관계
A B— descendant. A 안 어디든 B.article p= 어떤<article>안 어디든 모든<p>.A > B— direct child.ul > li= 최상위<li>만, 중첩 안 됨.A + B— adjacent sibling.h2 + p=<h2>바로 다음의<p>.A ~ B— general sibling.h2 ~ p= 같은 레벨에서<h2>뒤의 모든<p>.
Logical selector: 모던 파워 툴
:is(a, b, c)— 안쪽 selector 어느 하나라도 매치하면 매치.:is(h1, h2, h3) a= h1, h2, h3 안의 앵커. Specificity = argument 중 가장 높은 거.:where(a, b, c)— 같은 매칭, specificity 항상 0,0,0,0. 디자인 시스템 베이스 스타일의 마법.:not(a, b)— 안쪽 selector 어떤 것도 매치 안 하면 매치.li:not(:last-child)= 마지막 빼고 모든 리스트 항목.:has(selector)— 부모 selector.article:has(img)= 이미지 포함하는 article.form:has(input:invalid)= 유효하지 않은 입력 가진 폼.
Selector list: comma 로 구분
h1, h2, h3 { font-family: sans-serif; } — 리스트의 모든 selector 에 같은 규칙 적용. 각자 독립적으로 평가되고, 그러고 나서 매치마다 specificity 계산. (참고: :is() 안의 리스트는 하나의 논리 그룹으로 다뤄짐; 그게 차이.)
오른쪽-왼쪽 매칭이 브라우저가 하는 방식. CSS selector 가 오른쪽-왼쪽으로 평가 (가장 오른쪽 simple selector 가 먼저 매치, 그 다음 위로 따라감). 그래서
* { ... } 가 가장 느린 selector (모든 element 가 매치; 그 다음 각자 ancestor 체크). 퍼포먼스는 거대 스케일에서만 중요 — 보통 사이트는 가장 명료한 selector 쓰고 브라우저가 최적화하게 해.피파의 노트
Cwk-site 의 Tailwind 클래스가
.flex, .gap-4, .text-amber-500 같은 selector 로 풀려 — 모두 simple class selector. Tailwind 가 모든 utility 에 (0,0,1,0) specificity 주고 source 순서에 의존해서 cascade 전쟁을 피했어. Selector 동물원 안다는 건 Tailwind 가 그 전략을 왜 골랐는지 이해한다는 거지 단지 작동한다는 거에 그치지 않아.