Velog
Tailwind CSS에서 자식 요소 타겟팅하기
CMS HTML 처럼 클래스를 붙일 수 없는 자식 요소 를 Tailwind 로 스타일하는 법. [&_a]:..., [&>div]:... 임의 변형(arbitrary variant) 문법의 원리와 한계.
핵심 요약
세 가지 패턴을 기억하면 대부분의 케이스를 커버한다.
| 패턴 | 문법 | CSS 결과 |
|---|---|---|
| 직접 자식 | [&>div]:p-4 | .class > div { padding: 1rem } |
| 후손 | [&_a]:underline | .class a { text-decoration: underline } |
| 자식 + 가상 상태 | [&>button:hover]:bg-blue-600 | .class > button:hover { background: ... } |
핵심 규칙 두 가지:
&= 현재 생성되는 클래스 선택자- 공백은 언더스코어(
_) 로 작성 (Tailwind 파서 제약)
// CMS 콘텐츠 전용 래퍼 컴포넌트
<article
className="p-4
[&_a]:font-semibold [&_a:hover]:underline
[&_img]:rounded-lg [&_img]:max-w-full
[&_li]:list-disc [&_li]:ml-6"
>
{cmsHtml}
</article>
Tailwind 의 철학은 '모든 요소에 직접 유틸리티 클래스'. 그러나 CMS / 3rd-party / 동적 HTML 은 이 전제가 깨진다. 임의 변형 [&...] 은 SCSS/CSS nesting 의 & 와 동일 의미 로, 현재 요소의 생성된 클래스 셀렉터가 그 자리에 치환된다. 자식/후손/가상 상태 모두 표현 가능하다.
이 문법을 모르면 CMS 콘텐츠를 스타일할 때 @tailwindcss/typography (prose) 에 매달리거나 전용 CSS 파일을 따로 만들어야 한다. 문제는 전용 CSS 가 Tailwind JIT 와 퍼지(purge) 규칙 밖 이라는 점 — 번들 크기 예측이 깨진다. [&_a]:underline 은 그대로 JIT 스캔 대상이라 사용한 것만 최종 CSS 로 남는다.
학습 포인트
면접 답변으로 연결할 학습 포인트입니다.
`&` 의 의미는 SCSS 와 똑같다 — 치환이 생성된 클래스 이름
<div class="[&_a]:font-semibold"> 이 만드는 CSS 는:
.\[\&_a\]\:font-semibold a {
font-weight: 600;
}
& 자리에 이스케이프된 클래스 이름 이 들어가고, 그 뒤에 a 후손 셀렉터가 이어진다. SCSS 의 .parent { & a { ... } } 과 같은 논리.
[&.foo] 처럼 같은 요소의 추가 클래스를 노리는 것. 가능은 하지만 복수 클래스 조합은 가독성이 급격히 떨어진다 — 이 경우는 그냥 별도 유틸 클래스로 분리하는 게 낫다.
공백은 `_`, 진짜 `_` 는 `\_` — Tailwind 파서의 현실 제약
클래스 이름에 공백이 올 수 없어 CSS 결합자(combinator) 간 공백을 _ 로 쓴다.
<!-- 후손 a 요소 -->
<div class="[&_a]:underline">...</div>
<!-- 직접 자식 div -->
<div class="[&>div]:p-4">...</div>
<!-- 속성 셀렉터 값에 _ 가 들어가야 하면 이스케이프 -->
<div class="[&[data-state=\_active]]:bg-blue-500">...</div>
[& a] 처럼 실제 공백을 쓰는 것. JIT 스캔에서 클래스가 인식되지 않아 CSS 가 생성되지 않는다.
이 문법이 정답이 아닐 때가 많다
저자도 반복해서 말한다 — 대부분의 경우 전용 .cms-content a { ... } 스타일시트가 더 낫다.
| 상황 | 권장 접근 |
|---|---|
| 규칙 1~2개, 컴포넌트와 같은 위치 유지 | 임의 변형 |
| 규칙 10개+, 유지보수가 중심 | 전용 CSS 또는 @tailwindcss/typography |
prose 로 커버되는 일반 글 | className="prose" |
| CMS 마크업 + 커스텀 디자인 | prose 확장 또는 전용 CSS |
'테일윈드를 쓰니까 모든 걸 테일윈드로' 가 아니다.
컨테이너에 임의 변형을 20개 이상 붙이는 것. 코드 리뷰어가 읽을 수 없어 PR 이 막힌다.
읽는 순서
- 1이론
Tailwind 공식
Handling arbitrary variants문서에서&,_,>,~,+결합자 표기를 정리. - 2구현
Tailwind Play 에서
[&_a]:underline [&_a:hover]:text-blue-500을 적용한 div 에<a>를 넣어 JIT 결과 CSS 를 확인. - 3실무
현재 프로젝트에서
dangerouslySetInnerHTML이나 Markdown 렌더러 사용처를 찾아, 임의 변형으로 커버 가능한지 /prose로 충분한지 / 전용 CSS 가 나은지 판단. - 4설명
'CMS HTML 스타일 3가지 방법' 을 정리해 팀에 공유. 각각이 맞는 상황을 한 줄씩 예시로.
면접 연결 질문
[감점 답변] 'prose 쓰면 된다' 만. [좋은 답변] 세 가지 옵션을 비교: (1) @tailwindcss/typography 의 prose — 기본 스타일 좋지만 디자인 커스터마이징 복잡, (2) [&_a]:... 임의 변형 — 컴포넌트와 스타일이 같은 위치, 하지만 규칙이 많으면 불가독, (3) 전용 .cms-content CSS 파일 — 가장 가독성 높음, 단 번들 퍼지 대상 외. 규칙 수와 디자인 복잡도로 결정.
[감점 답변] '비슷하다'. [좋은 답변] _ = 모든 후손(descendant), > = 직접 자식. CMS 콘텐츠처럼 <p><a>...</a></p> 구조에서는 전자만 매칭된다. CSS 의 ' ' vs ' > ' 결합자 차이와 정확히 같다.
자기 점검
& 가 HTML 의 부모 요소라고 오해. 실제로는 이 클래스가 만들어내는 CSS 셀렉터 자신.