- 발행일
Tailwind CSS 정리 — 그리고 이 글이 틀렸던 것: 클래스 순서는 우선순위가 아니다
Tailwind CSS 정리
유틸리티 클래스 기반 CSS 프레임워크 Tailwind의 레퍼런스 정리입니다. 앞부분은 문법 정리, 뒷부분은 원문에 있던 틀린 내용의 정정입니다 — 이 정정이 이 글에서 제일 중요한 부분이라, 급하면 마지막 절만 읽어도 됩니다.
공식 문서: https://tailwindcss.com/docs
1. 장단점 요약
| 장점 | 단점 |
|---|---|
| 클래스만으로 빠른 UI 구성, 네이밍 고민 제거 | 마크업이 길어져 가독성 저하 |
| 디자인 토큰(간격·색상)이 시스템으로 강제됨 | 토큰 구조를 익히는 러닝커브 |
| 미사용 클래스 빌드 시 제거 → 작은 산출물 | 반복 스타일은 컴포넌트/@apply로 묶어야 함 |
sm: md: 접두사로 반응형이 간결 | 로직과 스타일이 한 파일에 섞임 |
2. 핵심 문법 레퍼런스
단위 — 기본은 rem(1 = 0.25rem = 4px): mt-1=4px, mt-4=16px, w-1/2=50%, 임의값은 w-[300px].
색상 — 색이름-명도 조합(text-red-600, bg-yellow-100), 명도 50~950, 임의값 bg-[#123456].
여백 방향 — p/m + t·b·l·r·x·y: px-6(좌우 패딩), mx-4(좌우 마진).
타이포 — text-xl, font-bold, truncate, line-clamp-2, tracking-wide, leading-6.
보더·그림자 — border-2 border-dashed rounded-lg, shadow-md, shadow-inner.
레이아웃 — relative/absolute + top-0, z-10, aspect-video.
<!-- Flex: 부모에 선언, justify(주축)·items(교차축)·gap -->
<div class="flex justify-between items-center gap-4">...</div>
<!-- Grid: 2차원 — 열 정의 + span 병합 -->
<div class="grid grid-cols-3 gap-4">
<div class="col-span-2">2칸</div>
<div>1칸</div>
</div>
<!-- 반응형: 모바일 퍼스트 — 기본이 모바일, 상위 해상도에서 덮어쓰기 -->
<div class="flex flex-col md:flex-row">...</div>
| 접두사 | 최소 너비 | 상태 접두사 | 의미 | |
|---|---|---|---|---|
sm: | 640px | hover: focus: active: | 자기 상태 | |
md: | 768px | group-hover: | 부모 상태에 반응 | |
lg: | 1024px | peer-focus: | 형제 상태에 반응 | |
xl: | 1280px | [&>p]:text-sm | 임의 선택자(자식 등) |
<!-- group: 카드 전체에 호버하면 내부 요소가 반응 -->
<a class="group">
<h3 class="group-hover:text-pink-500">타이틀</h3>
<p class="hidden group-hover:block">설명</p>
</a>
3. 커스터마이징
// tailwind.config.js
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx,html}'],
theme: {
extend: {
colors: { primary: '#1F6FEB' },
fontFamily: { sans: ['Noto Sans KR', 'sans-serif'] },
},
},
}
디자인 시스템의 고정 색상·폰트를 extend에 등록하면 text-primary처럼 시맨틱한 이름으로 쓸 수 있습니다. VS Code에서는 공식 IntelliSense 확장이 자동완성과 클래스 미리보기를 제공합니다.
4. 정정 — "마지막 클래스가 우선"은 틀렸다
원래 이 글에는 "같은 속성이 겹치면 마지막에 선언된 클래스가 적용된다"는 전제로 쓴 우선순위 표가 세 개나 있었습니다(text-sm text-lg → text-lg 승리, bg-red-500 bg-blue-500 → blue 승리…). 개고하면서 검증하다 알았는데, 이 전제 자체가 틀렸습니다.
CSS는 class 속성 안에서의 순서를 보지 않습니다. 두 클래스의 명시도(specificity)가 같으면 승자는 스타일시트에서 나중에 정의된 쪽이고, Tailwind가 생성하는 스타일시트의 순서는 내가 HTML에 쓴 순서와 무관하게 Tailwind의 내부 정렬을 따릅니다.
<!-- 두 줄의 결과는 완전히 동일하다 — HTML상 순서는 아무 영향이 없다 -->
<p class="text-sm text-lg">...</p>
<p class="text-lg text-sm">...</p>
원문 표의 답이 몇 개 맞아 보였던 건 우연입니다 — 크기 계열(text-sm < text-lg)은 생성 CSS에서 큰 쪽이 뒤에 오는 경우가 많아 "마지막 승리"처럼 보였을 뿐이에요. 색상처럼 정렬 기준이 직관적이지 않은 조합에서는 어느 쪽이 이길지 class 문자열만 봐서는 알 수 없습니다.
이 사실이 실무에서 중요한 이유는 조건부 클래스 병합 때문입니다:
// 컴포넌트 기본 스타일을 호출부에서 덮어쓰려는 흔한 시도 — 동작을 보장할 수 없다
<Button className="bg-red-500" /> // Button 내부: "bg-blue-500 px-4 ..."
"내가 나중에 넘겼으니 red가 이기겠지"가 성립하지 않으니, 이 문제를 풀려고 존재하는 게 tailwind-merge(twMerge) 입니다 — 문자열을 파싱해 같은 속성 그룹의 앞선 클래스를 제거해 버리는 방식으로, CSS 우선순위 바깥에서 승부를 결정해요. 라이브러리가 존재한다는 것 자체가 "순서로 해결 안 된다"는 증거였는데, 정리할 때는 거기까지 생각이 닿지 않았습니다.
원문에서 맞았던 것도 남겨두면 — p-4 px-2처럼 적용 범위가 다른 조합(전체 vs 좌우)은 순서 문제가 아니라 속성 자체가 달라 부분 덮어쓰기가 되고, hover:·md:처럼 조건이 다른 클래스는 애초에 충돌하지 않으며, !text-blue-500(important)은 실제로 최우선입니다.
덤: 원문의 @apply 예제도 돌지 않는 코드였다
.btn { @apply px-4 py-2 rounded-lg; }
.btn-primary { @apply btn bg-blue-600; } /* ❌ 에러 */
@apply는 유틸리티 클래스에만 쓸 수 있습니다. 직접 정의한 .btn 같은 커스텀 클래스를 @apply btn으로 참조하면 "class does not exist" 빌드 에러가 나요. 공통 버튼을 만들려면 CSS 상속이 아니라 유틸리티 나열을 반복하거나, 컴포넌트 계층(React 컴포넌트)에서 합성하는 게 Tailwind의 방식입니다.
돌아보면
- "그럴듯한 규칙"을 실행 없이 표로 만들면 오류도 표의 권위를 얻습니다. 우선순위 표 세 개는 전부 한 번도 브라우저에서 검증하지 않은 채 쓰였고, 그래서 2년 가까이 틀린 채로 있었어요. 레퍼런스 글일수록 예시는 돌려보고 실어야 합니다.
- 한계 — 이 글은 Tailwind v3 기준입니다. v4는 설정이 CSS 파일 기반(
@theme)으로 바뀌는 등 구성 방법이 달라져, 3절의 config 예시는 v4에서 그대로 통하지 않습니다.