당신은 TailwindCSS를 잘못 사용하고 있습니다
I’m happy to translate the article for you, but I need the text you’d like translated. Please paste the content (excluding the source line you already provided) and I’ll return a Korean version that preserves the original formatting, markdown, and technical terms.
저는 이전에 왜 일반적으로 Tailwind CSS를 프로젝트의 주요 스타일링 접근 방식으로 사용하는 것을 권장하지 않는지 언급했으며, 그 입장을 여기에서 자세히 설명했습니다.
이번에는 보다 실용적인 관점에서 Tailwind CSS를 반패턴으로 만들지 않으면서 올바르게 사용할 수 있는 방법에 대해 이야기하고자 합니다.
웹 스타일링의 짧은 역사
옛날 옛적에 행복한 공룡들이 살았다고… 네, 그건 너무 오래전 이야기죠. 현대 HTML과 CSS가 널리 사용되기 시작한 시점, 즉 HTML5와 CSS3가 안정화되고 웹이 급속히 확장되기 시작한 시점으로 돌아가 보겠습니다.
그 시기에 트위터는 Bootstrap을 선보였으며, 이는 최초의 널리 채택된 컴포넌트 기반 CSS 라이브러리 중 하나였습니다. Bootstrap은 당시 혁신적이었습니다: 일관된 시각 언어, 합리적인 기본값, 그리고 인터페이스 구축에 필요한 노력을 크게 줄여주는 즉시 사용 가능한 컴포넌트를 제공했기 때문입니다.
하지만 Bootstrap에는 몇 가지 트레이드오프가 있었습니다:
- 매우 의견이 강한 디자인 시스템을 함께 제공했기 때문에 많은 웹사이트가 동일하게 보이기 시작했습니다.
- 스타일을 덮어쓰고 테마를 사용할 수는 있었지만, 그 오버플로우는 불가피했습니다.
- Bootstrap은 고정된 HTML 구조를 요구했으며, 요소를 하나라도 변경하면 전체 스타일이 깨질 수 있었습니다.
- 또한 전체 구조를 기억하거나 문서에서 복사‑붙여넣기 해야 했습니다.
컴포넌트 추상화의 부상
JS 프레임워크인 React와 Vue의 부상으로 개발자 경험(DX)이 크게 개선되었습니다. 이제 HTML 마크업과 클래스를 캡슐화하고 props/attributes를 통해 깔끔한 API를 노출함으로써 더 높은 수준의 추상화를 달성할 수 있게 되었습니다.
추상화 이전 (Bootstrap v5 모달 마크업)
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Modal body text goes here.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
추상화 이후 (MUI v7.x 모달 컴포넌트)
<Modal open={open} onClose={handleClose}>
<Box sx={{ p: 2 }}>
Modal body text goes here.
</Box>
</Modal>
여기서 구조적 복잡성은 컴포넌트 내부에 숨겨집니다. 사용자는 원시 마크업과 깨지기 쉬운 클래스 구조 대신 명확한 API와 상호작용합니다.
유틸리티 CSS가 해결하려 했던 실제 문제
일반적인 상황을 상상해 보세요: 요소의 인라인 시작 부분에 5 px 정도의 작은 패딩이 필요합니다. 그래서 .padding-inline-start-5px 같은 클래스를 만들죠. 나중에 7 px가 필요해지면 .padding-inline-start-7px를 추가합니다. 시간이 지나면서 이 방식은 수십, 심지어 수백 개의 마이크로‑클래스로 폭발하고, .padding-inline-start-7.8px처럼 의문스러운 클래스까지 생겨납니다.
Tailwind CSS는 실제로 좋은 아이디어를 도입했습니다: utility‑first CSS. 임의의 클래스 이름과 값을 만들기보다는, Tailwind는 간격, 색상, 타이포그래피 등 다양한 속성을 위한 제한적이고 일관된 스케일을 제공합니다. 이 시스템은 선택 피로도를 줄이고 코드베이스 전반에 걸쳐 시각적 일관성을 강제합니다.
그 부분은 객관적으로 강점이 있습니다.
그렇다면 Tailwind CSS에 대한 반발은 왜 일어날까?
Tailwind CSS가 정말 똑똑하다면, 왜 많은 개발자들(나 자신 포함)이 종종 비판적인가?
내 겸손한 의견으로는, 문제는 유틸리티 클래스 개념 자체가 아니라, Tailwind가 일반적으로 소개되고 채택되는 방식이다. Tailwind는 또 다른 추상화 레벨을 가진 프레임워크로 제시되었으며, CSS 부분만 제공하고 HTML이나 JavaScript는 포함되지 않았고, 원하는 대로 자유롭게 사용할 수 있다고 했다.
그 자유와 급속한 인기가 결합되면서 혼란이 생겼다—특히 초보자들 사이에서. 많은 사람들이 CSS 자체를 제대로 이해하기 전에 Tailwind CSS를 배우기 시작했다. 스태킹 컨텍스트, 마진 붕괴, 레이아웃 흐름 문제와 같은 개념은 기본 언어를 배우지 않으면 여전히 신비롭게 남는다.
공정하게 말하자면, 이것은 Tailwind CSS에만 국한된 문제가 아니다. JavaScript에 대한 탄탄한 이해 없이 React/Vue를 사용하는 개발자들에게도 같은 패턴이 존재한다. 하지만 Tailwind는 UI를 만들 때 CSS를 전혀 작성하거나 깊이 이해하지 않아도 되게 함으로써 이 문제를 무의식적으로 확대한다.
내가 Tailwind CSS를 개인적으로 사용하는 방법
For me, Tailwind CSS는 스타일링 철학이 아니라 유틸리티 라이브러리이다. 나는 JavaScript에서 Lodash를 다루는 방식과 동일하게 다룬다—일반적인 작업을 더 쉽게 해 주는 유용한 도구들의 모음이며, 언어 자체를 대체하는 것이 아니다.
이것이 내가 UnoCSS 같은 프로젝트를 높이 평가하는 이유이기도 하다. 이 프로젝트는 이 아이디어에 크게 의존하고 보다 유연한 방향으로 확장하지만, 완전한 독립 프레임워크가 되려 하지는 않는다.
If I choose to use Tailwind CSS, my approach is very strict:
- 핵심 컴포넌트는 의미론적 클래스 이름을 가진 순수 CSS로 작성한다.
- Tailwind의 CSS 변수를 디자인 토큰(간격, 색상, 타이포그래피)에 사용한다.
- 레이아웃, 애니메이션, 호버 상태 및 복잡한 인터랙션은 인라인 유틸리티 체인이 아니라 CSS 클래스에 정의한다.
- 유틸리티 클래스는 일회성 조정에만 예외적으로 사용하며, 기본은 아니다.
Source: …
예시
Tailwind CSS의 문제적인 사용
<!-- Example omitted in original source -->
위 예시는 레이아웃, 포지셔닝, 시각적 스타일링, 인터랙션 상태를 모두 하나의 긴 유틸리티 체인에 섞어 넣어 마크업을 읽고 유지보수하기 어렵게 만들고 있습니다.
보다 실용적인 접근법
<div class="card">
<!-- content -->
</div>
/* card.css */
.card {
@apply flex items-center justify-center;
@apply absolute text-center bg-white rounded-xl shadow-md p-4;
@apply hover:bg-gray-100 transition-colors duration-200;
}
여기서는 컴포넌트의 의도가 명확히 드러납니다(card). Tailwind의 @apply 지시자를 전용 CSS 파일 안에서 사용해 HTML을 깔끔하게 유지합니다.
디자인 토큰 전용 Tailwind 사용
/* design-tokens.css */
:root {
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--color-primary: #1d4ed8;
--color-primary-hover: #1e40af;
}
/* Example usage */
.button {
@apply bg-primary text-white py-[var(--spacing-sm)] px-[var(--spacing-md)];
transition: background-color var(--duration-medium);
}
.button:hover {
background-color: var(--color-primary-hover);
}
Tailwind 설정이 스케일을 제공하지만 실제 스타일링은 CSS에 존재하여 관심사의 분리를 유지합니다.
요점
- Utility‑first는 강력하지만, 의미론적이고 유지보수 가능한 CSS를 보완해야 하며—대체해서는 안 됩니다.
- Tailwind를 도구 상자처럼 사용하고, 거대한 프레임워크처럼 사용하지 마세요.
- 핵심 컴포넌트 마크업은 의미론적으로 유지하고 Tailwind가 저수준 토큰(여백, 색상, 타이포그래피)을 제공하도록 하세요.
- 인라인 유틸리티 체인은 빠른 일회성 수정에만 사용하고, 그렇지 않다면 재사용 가능한 CSS 클래스로 추출하세요.
이러한 가이드라인을 따르면 Tailwind CSS의 생산성 향상을 누리면서도 종종 비판받는 함정을 피할 수 있습니다. 스타일링을 즐기세요!
정리된 마크다운
<div class="hello-tailwind">
## Hello Tailwind
</div>
레이아웃, 시각 디자인, 의미론을 혼합하는 것이 왜 문제인가
이 접근 방식은 레이아웃, 시각 디자인, 의미론을 마크업에 직접 혼합합니다.
이제 이 카드를 5개의 서로 다른 위치에 약간씩 변형해서 사용해야 한다고 상상해 보세요. 매번 전체 클래스 문자열을 복사하고 수정해야 합니다. 디자인 요구 사항이 변경될 때(항상 그렇듯이) 모든 인스턴스를 찾아 개별적으로 업데이트해야 합니다.
더 깔끔한 대안: Tailwind의 CSS 변수 사용
<div class="card">
## Hello World
</div>
<style>
.card {
/* Layout and behavior */
display: flex;
position: absolute;
text-align: center;
/* Tailwind design tokens */
background-color: rgb(var(--color-white));
border-radius: calc(var(--radius) * 3); /* rounded‑xl */
box-shadow: var(--shadow-lg);
padding: calc(var(--spacing) * 6); /* p‑6 */
width: calc(var(--spacing) * 80); /* w‑80 */
}
</style>
수정자 추가
.card {
/* …base styles… */
}
/* Modifier: primary */
.card.is-primary {
background-color: rgb(var(--color-blue-500));
color: rgb(var(--color-white));
}
/* Modifier: secondary */
.card.is-secondary {
background-color: rgb(var(--color-gray-100));
color: rgb(var(--color-gray-800));
}
Note: 이것은 실제 CSS입니다. 중첩은 이제 언어 자체의 일부이며—전처리기가 필요 없습니다. CSS는 아직도 진화하고 있으며, 많은 사람들이 생각하는 것보다 더 강력합니다. 자세한 내용은 MDN을 참고하세요.
TailwindCSS 유틸리티 클래스를 합리적으로 사용하는 방법
<div class="hello-world">
## Hello World
</div>
일반적인 반론 다루기
계속 진행하기 전에, 순수 유틸리티‑우선 접근 방식에 대한 흔한 주장을 살펴보겠습니다:
-
“하지만 콜로케이션이 컴포넌트를 더 이식 가능하게 만든다!”
맞지만, 절대 변경할 필요가 없을 때만 해당됩니다. 약간씩 다른 여러 인스턴스가 생기면 전체 클래스 문자열을 복제하거나 어쨌든 래퍼 컴포넌트를 만들게 됩니다. Tailwind 변수와 함께 사용하는 의미론적 클래스는 동일한 이식성을 제공하면서 유지 보수성을 높여줍니다. -
“CSS 파일이 부피가 커지고 관리하기 어려워진다!”
이는 컴포넌트‑스코프 CSS와 CSS 모듈이 등장하기 전의 상황이었습니다. 최신 도구(CSS Modules, Vue scoped styles, CSS‑in‑JS, Svelte 등)는 이 문제를 완전히 해결합니다. 컴포넌트 스타일은 컴포넌트와 함께 존재합니다. -
“사용되지 않는 CSS를 찾는 것이 사용되지 않는 유틸리티를 찾는 것보다 어렵다!”
컴포넌트‑스코프 스타일에서는 컴포넌트를 삭제하면 사용되지 않는 CSS가 자동으로 제거됩니다. Tailwind 유틸리티의 경우 PurgeCSS가 도움이 되지만, 동적 클래스 이름에 대해서는 여전히 주의가 필요합니다.
TailwindCSS가 실제로 의미가 있는 경우
TailwindCSS가 단순히 의미가 있을 뿐만 아니라 바이브 코딩이라는 현대적인 상황에서는 오히려 올바른 선택이 될 수 있는 경우가 하나 있습니다.
AI 도구로 프로토타이핑을 할 때 주요 목표는 종종 속도와 시각적 정확성이며, 장기적인 유지보수성은 크게 고려되지 않습니다. 화면에 보이는 결과가 중요하고, CSS 구조의 우아함은 크게 신경 쓰지 않습니다. 이런 상황에서는 의미 있는 클래스 이름을 설계하거나, 추상화 레이어를 고민하거나, 스타일을 신중하게 구조화하는 것이 불필요한 마찰처럼 느껴질 수 있습니다.
유틸리티‑퍼스트 CSS가 여기서 잘 맞는 이유는 다음과 같습니다:
- 피드백 루프가 매우 빠릅니다.
- 스타일이 AI가 생성하거나 수정하는 마크업과 가깝게 위치합니다.
- CSS 파일을 직접 작성하고 디버깅하는 작업을 피할 수 있습니다.
- 시각적인 미세 조정이 간단하고 일시적입니다.
즉, TailwindCSS는 탐색적이고 일회성인 사고방식과 매우 잘 맞습니다. 코드가 오래 살아남지 않거나, 신중하게 진화되지 않으며, 팀이 유지보수할 필요가 없는 경우라면 구조보다 속도를 최적화하는 것이 합리적인 선택이 됩니다.
이것이 Tailwind가 AI 도구와 자연스럽게 짝을 이루는 이유이기도 합니다. 대규모 언어 모델은 유틸리티 클래스 문자열을 구성하는 데는 능숙하지만, 서로 다른 파일에 걸쳐 일관되고 진화하는 CSS 아키텍처를 유지하는 데는 훨씬 못합니다.
최종 생각
TailwindCSS는 잘못되지 않다. CSS를 대체하는 용도로 사용하는 것은 맞다. 탄탄한 CSS 지식과 컴포넌트 설계 위에 유틸리티 레이어로 활용하면 매우 효과적일 수 있다. CSS 기본을 배우는 것을 피하기 위한 지름길로 사용하면 빠르게 기술 부채가 된다.