빌드노트
.card:nth-child(2)가 두 번째 카드 대신 첫 번째 카드를 고르는 이유
제목과 안내문이 끼어 있는 카드 목록에서 nth-child, nth-of-type, nth-child의 of 문법이 무엇을 세는지 실제 ID와 화면 강조로 비교했다. 클래스별 두 번째 카드를 선택하는 수정과 브라우저 지원 확인 방법을 설명한다.
카드 앞에 제목을 넣고 카드 사이에 안내문을 추가했더니 .card:nth-child(2)의 강조가 첫 번째 카드로 옮겨갈 수 있다. 2가 세는 대상은 카드 목록이 아니라 같은 부모 아래의 모든 형제 요소이기 때문이다. .card는 그중 두 번째 요소가 카드인지 제한할 뿐, 카드를 먼저 모아 세게 만들지 않는다.
원하는 것이 “이 부모의 .card 중 두 번째”라면 :nth-child(2 of .card)가 그 기준을 표현한다. of .card로 형제를 걸러 낸 뒤 순서를 센다. 두 문법의 차이는 W3C Selectors Level 4의 nth-child 정의 (새 탭에서 열림)에 명시돼 있다.
제목과 공지가 끼어 있는 목록에서 확인하기
아래 내용을 UTF-8 HTML 파일로 저장해 새 브라우저 탭에서 열면, 기존 선택자가 어느 카드를 강조하는지 확인할 수 있다. 여기서 카드는 c1, c2, c3이고 notice에는 .card가 없다.
<meta charset="utf-8">
<style>
#cards > .card:nth-child(2) {
background: #ffe08a; outline: 4px solid #b15200;
}
</style>
<div id="cards">
<h2 id="heading">추천 카드</h2>
<div class="card" id="c1">첫 번째 카드</div>
<p id="note">목록 안내</p>
<div id="notice">카드가 아닌 공지</div>
<div class="card" id="c2">두 번째 카드</div>
<div class="card" id="c3">세 번째 카드</div>
</div>
첫 형제 요소는 heading, 둘째는 c1이다. 그래서 .card:nth-child(2)는 c1을 선택한다. .card를 붙였는데 안내문까지 세는 것은 오류가 아니라 선택자의 의미다. 반대로 둘째 요소가 .card가 아닌 경우에는 선택 결과가 비게 된다. 들여쓰기와 줄바꿈으로 생긴 텍스트 노드는 이 요소 순서에 포함되지 않는다.
이 구조를 Windows 11의 Chrome 156.0.8078.47, headless 모드에서 실행했다. 개발자 도구 Console에서는 다음 입력으로 지원 여부와 실제 매칭 ID를 비교할 수 있다. 기존 화면을 조사할 때는 #cards를 실제 부모 선택자로 바꾼다. 첫 줄이 false라면 마지막 줄의 of .card 조회는 실행하지 않는다. 아래 주석은 이번 실행 결과다.
CSS.supports("selector(:nth-child(2 of .card))"); // true
const ids = selector =>
Array.from(document.querySelectorAll(selector), el => el.id);
ids("#cards > .card:nth-child(2)"); // ["c1"]
ids("#cards > div:nth-of-type(2)"); // ["notice"]
ids("#cards > .card:nth-of-type(2)"); // []
ids("#cards > :nth-child(2 of .card)"); // ["c2"]
화면에서도 같은 선택자를 하나씩 적용했다. .card:nth-child(2)에서는 첫 카드가, div:nth-of-type(2)에서는 공지가 강조됐고, .card:nth-of-type(2)에서는 강조가 없어졌다. 마지막 선택자에서는 둘째 카드가 강조됐다.
nth-of-type은 같은 클래스가 아니라 같은 태그를 센다
nth-of-type(2)로 바꾸면 제목과 p를 건너뛰므로 해결된 것처럼 보일 수 있다. 실제로 위 예제에서 notice를 빼면 .card:nth-of-type(2)는 c2를 선택했다. 하지만 공지 div를 다시 넣자 결과는 빈 배열이 됐다.
이때 div끼리의 순서는 c1 → notice → c2 → c3이다. 두 번째 div인 notice에는 .card가 없으므로 .card:nth-of-type(2)에 맞는 요소가 없다. 앞에 .card를 붙여도 타입의 기준은 클래스가 되지 않는다. MDN의 nth-of-type 설명 (새 탭에서 열림)도 타입을 태그 이름으로 구분한다.
타입이 섞인 카드도 따로 확인했다. c2와 c3의 태그만 article로 바꾸자 .card:nth-of-type(2)는 두 번째 article인 c3를 선택했다. 반면 :nth-child(2 of .card)는 계속 c2를 선택했다. 태그가 아니라 카드 클래스의 순서가 요구사항이라면 nth-of-type으로 대체하면 안 되는 이유다.
카드만 걸러 세도록 규칙 바꾸기
위 HTML의 style 안에 있는 강조 규칙을 다음 하나로 교체하면 .card 형제 중 두 번째에 배경과 외곽선이 생긴다.
#cards > :nth-child(2 of .card) {
background: #ffe08a; outline: 4px solid #b15200;
}
이 규칙은 .card인 형제만 c1 → c2 → c3으로 세므로 c2를 선택한다. >는 #cards의 바로 아래 자식으로 적용 범위를 제한한다. 카드가 wrapper 안으로 들어가면 그 부모 구조에 맞춰 선택자도 조정해야 한다. MDN의 of 선택자 문법 (새 탭에서 열림)에서도 함수 안에서 거르는 것과 함수 바깥에 클래스를 붙이는 것을 구분한다.
비교 실행에서 카드 div 세 개만 남겼을 때는 세 방식 모두 c2를 선택했다. 제목과 안내 요소가 추가된 뒤에도 카드 순서를 유지하려면, 지금 우연히 같은 결과가 나오는지가 아니라 무엇을 세기로 했는지를 기준으로 고른다. 전체 형제의 둘째 위치가 목적이면 nth-child(2), 같은 태그의 둘째 위치가 목적이면 nth-of-type(2), .card끼리의 둘째 위치가 목적이면 nth-child(2 of .card)다.
지원 확인에 쓴 CSS.supports() (새 탭에서 열림)는 해당 브라우저가 선택자 문법을 지원하는지 알려 준다. true여도 카드가 실제로 두 개 이상 있는지, 같은 부모의 자식인지, 다른 CSS가 강조를 덮는지는 매칭 결과와 개발자 도구의 Styles에서 확인해야 한다.
이번 검증은 새 로컬 HTML에서의 선택 결과와 화면 강조까지다. Firefox·Safari·구형 Chrome이나 내장 WebView에서 of .card가 동작하는지는 실행하지 않았다. 기본 nth-child 지원을 of 문법 지원으로 간주하면 안 된다. 미지원 브라우저도 대상이라면 카드만 자식으로 두는 목록에서 nth-child(2)를 쓰거나, 렌더링할 때 대상 카드에 명시적인 클래스를 부여하는 방식을 검토한다. 그 대체 경로의 실제 대상 브라우저 검증은 별도로 필요하다.