얻은 것
충돌 없음. 구조가 이름에 드러남. 특이도가 평평해짐.
“클래스가 길어서 못 봐주겠다”는 첫인상은 정당하다. 그 대가로 무엇을 얻는지가 문제다
이 장은 Tailwind 문법을 다루지 않는다. CSS가 왜 어려웠는지를 먼저 정리한다. 그래야 긴 클래스가 비용인지 대가인지 판단할 수 있다.
.title은 온 세상에 하나다. 다른 파일의 .title과 충돌한다.
어떤 파일이 어떤 클래스를 정의했는지 코드만 봐서는 알 수 없다.
.card .title이 .title을 이긴다. 이기려고 .page .card .title을 쓴다.
그것도 안 되면 !important. 한 번 시작되면 되돌릴 수 없다.
이 클래스를 쓰는 곳이 정말 없는지 확인할 방법이 없다. 그래서 아무도 안 지운다.
세 문제의 결과는 하나다 — CSS 파일은 단조 증가한다. 이것이 모든 CSS 방법론이 풀려던 문제다.
timeline
title CSS 스타일링 접근의 변천
2005 : 시맨틱 클래스 : .article-title
2010 : OOCSS · SMACSS : 재사용 단위를 정의하려는 시도
2013 : BEM : .block__element--modifier
2015 : CSS Modules : 빌드 타임에 이름을 유일하게
2016 : CSS-in-JS : styled-components · emotion
2017 : Tailwind : 이름 짓기를 포기한다
2025 : Tailwind v4 : CSS 자체가 설정 파일이 됨
.card { }.card__header { }.card__header--highlighted { }.card__body { }.card__body__list { } /* 규칙 위반이지만 현실에서 나온다 */얻은 것
충돌 없음. 구조가 이름에 드러남. 특이도가 평평해짐.
남은 문제
이름 짓는 데 시간이 든다. 컴포넌트 이름이 바뀌면 클래스도 전부 바뀐다. 여전히 아무것도 못 지운다. 팀원마다 규칙 해석이 다르다.
const Title = styled.h2` font-size: 1.25rem; color: ${(p) => (p.highlighted ? 'red' : 'black')};`얻은 것
컴포넌트와 스타일이 같은 파일에. 컴포넌트를 지우면 스타일도 지워진다. props로 동적 스타일.
남은 문제
런타임 비용 — 렌더마다 CSS를 만든다.
서버 컴포넌트와 상극 — Context와 런타임이 필요해 use client가 강제된다.
번들에 CSS 엔진이 포함된다.
이름을 잘 짓는 방법을 찾는 대신, 이름 짓기를 그만두자.
<div class="alert alert--warn"> <p class="alert__text">주의</p></div>.alert { display: flex; gap: .75rem; padding: .75rem 1rem; border-radius: .5rem; border: 1px solid #e4e4e7;}.alert--warn { border-color: #f59e0b;}파일 둘, 컨텍스트 전환 있음.
const Alert = styled.div` display: flex; gap: .75rem; padding: .75rem 1rem; border-radius: .5rem; border: 1px solid ${p => p.warn ? '#f59e0b' : '#e4e4e7'};`파일 하나. 대신 런타임 비용.
<div class="flex gap-3 px-4 py-3 rounded-lg border border-amber-500"> <p>주의</p></div>파일 하나. 컨텍스트 전환 없음. 런타임 없음.
세 방식 모두 결과 CSS는 사실상 같다. 차이는 “어디에 무엇을 쓰느냐”뿐이다.
가장 흔한 비판이다. “HTML에 스타일을 쓰면 관심사 분리 원칙 위반 아닌가?”
<div class="p-4 text-sm rounded-lg">/* 빌드 결과 — 딱 이만큼만 생성된다 */.p-4 { padding: 1rem }.text-sm { font-size: .875rem; line-height: 1.25rem }.rounded-lg { border-radius: .5rem }p-4를 100번 써도 CSS에는 한 번만 나온다flowchart LR
B["BEM · 5년 누적<br/>컴포넌트마다 새 클래스"] --> B2["CSS 가 단조 증가 ❌"]
M["CSS Modules<br/>파일마다 새 클래스"] --> M2["여전히 증가 ⚠️"]
T["Tailwind<br/>고정된 유틸리티 조합"] --> T2["규모와 거의 무관 ✅"]
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class B2 bad
class M2 warn
class T2 ok
class B,M,T mute
Tailwind 쪽이 작은 이유는 압축을 잘해서가 아니라 중복이 구조적으로 발생하지 않기 때문이다. 같은 스타일이 100군데 쓰여도 정의는 한 번뿐이다.
<!-- 이 조합을 20군데서 반복한다면? --><button class="inline-flex items-center justify-center rounded-md text-sm font-medium bg-zinc-900 text-white h-9 px-4 hover:bg-zinc-800">Tailwind의 답은 **“@apply를 쓰지 말고 컴포넌트를 만들어라”**다.
<Button> 컴포넌트 하나를 만들면 조합은 한 곳에만 존재한다.
그리고 그 <Button>을 처음부터 잘 만들어 주는 것이 shadcn/ui다.
마지막 항목은 우회 가능하다. CSS 변수를 쓰면 런타임에 바꿀 수 있다 — 12장·17장의 핵심 기법이 정확히 이것이다.