CSSMedium#21
CSS-in-JS vs CSS Modules vs Tailwind CSS의 장단점을 비교해주세요.
#CSS#스타일링#Tailwind#CSS-in-JS
힌트
스타일 캡슐화, 성능, 개발 경험의 측면에서 비교해보세요.
정답 및 해설
CSS-in-JS vs CSS Modules vs Tailwind CSS의 장단점을 비교해주세요.
현대 프론트엔드 개발에서 스타일을 작성하는 방법은 크게 세 가지 주류 접근법으로 나뉩니다. CSS-in-JS, CSS Modules, Tailwind CSS는 각각 다른 철학과 트레이드오프를 가지고 있으며, 프로젝트의 성격과 팀의 상황에 따라 적합한 선택이 달라집니다.
CSS-in-JS
CSS-in-JS는 JavaScript 파일 안에서 CSS를 작성하는 방식입니다. 대표적인 라이브러리로 styled-components와 Emotion이 있습니다.
기본 사용법 (styled-components)
import styled from 'styled-components';
// 컴포넌트와 스타일을 함께 정의
const Button = styled.button`
background: ${props => props.primary ? '#1976d2' : 'white'};
color: ${props => props.primary ? 'white' : '#1976d2'};
padding: 8px 16px;
border: 2px solid #1976d2;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
&:hover {
opacity: 0.85;
}
/* 미디어 쿼리도 중첩 가능 */
@media (max-width: 768px) {
width: 100%;
}
`;
// 사용
function App() {
return (
<div>
<Button>기본 버튼</Button>
<Button primary>강조 버튼</Button>
</div>
);
}
동적 스타일링
CSS-in-JS의 가장 큰 강점은 JavaScript의 모든 기능을 스타일에 활용할 수 있다는 점입니다.
// 테마 시스템
const theme = {
colors: {
primary: '#1976d2',
danger: '#d32f2f',
success: '#388e3c',
},
spacing: (factor) => `${factor * 8}px`,
};
const Card = styled.div`
background: ${({ theme }) => theme.colors.primary};
padding: ${({ theme }) => theme.spacing(2)};
border-radius: 8px;
`;
// 조건부 스타일
const Alert = styled.div`
padding: 12px 16px;
border-radius: 4px;
${({ variant }) => variant === 'error' && `
background: #ffebee;
border-left: 4px solid #f44336;
`}
${({ variant }) => variant === 'success' && `
background: #e8f5e9;
border-left: 4px solid #4caf50;
`}
`;
장단점
장점
- props 기반 동적 스타일링이 직관적
- 컴포넌트와 스타일이 같은 파일에 공존 (응집도 높음)
- 자동 벤더 프리픽스
- 미사용 스타일 자동 제거
- TypeScript와의 완벽한 통합
단점
- 런타임 오버헤드: 브라우저에서 스타일을 동적으로 생성하므로 성능 비용 발생
- 번들 크기 증가 (라이브러리 자체 용량)
- CSS 캐싱 불가 (동적 생성이므로)
- SSR 설정이 복잡할 수 있음
- React Server Components와 호환성 문제 (최신 Next.js App Router)
CSS Modules
CSS Modules는 일반 CSS 파일을 모듈로 불러오는 방식으로, 빌드 도구가 클래스 이름을 고유한 해시 값으로 변환하여 스코프를 보장합니다.
기본 사용법
/* Button.module.css */
.button {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.primary {
background: #1976d2;
color: white;
border: 2px solid #1976d2;
}
.secondary {
background: white;
color: #1976d2;
border: 2px solid #1976d2;
}
.button:hover {
opacity: 0.85;
}
// Button.tsx
import styles from './Button.module.css';
interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
}
function Button({ variant = 'primary', children }: ButtonProps) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
}
빌드 후 클래스 이름은 Button_button__3kHx9 같은 형태로 변환되어 충돌이 방지됩니다.
clsx와 함께 사용
import styles from './Card.module.css';
import clsx from 'clsx';
function Card({ isActive, hasError, children }) {
return (
<div
className={clsx(
styles.card,
isActive && styles.active,
hasError && styles.error
)}
>
{children}
</div>
);
}
장단점
장점
- 런타임 오버헤드 없음: 빌드 시점에 정적 CSS로 변환
- 기존 CSS 문법 그대로 사용 (학습 곡선 없음)
- CSS 캐싱 가능
- 모든 CSS 기능 사용 가능 (animations, @media, custom properties 등)
- React Server Components와 완벽 호환
단점
- 동적 스타일링이 번거로움 (CSS 변수를 활용하거나 인라인 스타일 필요)
- 스타일과 로직 파일이 분리됨
- 타입 안정성이 제한적 (추가 도구 필요)
- 전역 스타일 관리가 별도로 필요
Tailwind CSS
Tailwind CSS는 유틸리티 클래스(utility classes) 를 HTML에 직접 적용하는 방식입니다. CSS를 별도로 작성하지 않고, 미리 정의된 원자적(atomic) 클래스를 조합합니다.
기본 사용법
<!-- 전통적 CSS vs Tailwind -->
<!-- 전통적 방식 -->
<button class="btn btn-primary">버튼</button>
<!-- Tailwind 방식 -->
<button class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700
focus:outline-none focus:ring-2 focus:ring-blue-500
transition-colors duration-200">
버튼
</button>
React + Tailwind 예시
// 반응형 카드 컴포넌트
function ProductCard({ image, title, price, badge }) {
return (
<div className="relative bg-white rounded-xl shadow-md overflow-hidden
hover:shadow-xl transition-shadow duration-300">
{badge && (
<span className="absolute top-3 right-3 bg-red-500 text-white
text-xs font-bold px-2 py-1 rounded-full">
{badge}
</span>
)}
<img
src={image}
alt={title}
className="w-full h-48 object-cover"
/>
<div className="p-4">
<h3 className="text-lg font-semibold text-gray-900 truncate">
{title}
</h3>
<p className="text-xl font-bold text-blue-600 mt-2">
{price.toLocaleString()}원
</p>
<button className="mt-3 w-full bg-blue-600 text-white py-2 rounded-lg
hover:bg-blue-700 active:bg-blue-800
transition-colors duration-150">
장바구니 담기
</button>
</div>
</div>
);
}
반응형과 조건부 스타일링
// cn 유틸리티 (clsx + tailwind-merge)
import { cn } from '@/lib/utils';
function Alert({ variant, children }) {
return (
<div
className={cn(
'flex items-start gap-3 p-4 rounded-lg border',
{
'bg-red-50 border-red-200 text-red-800': variant === 'error',
'bg-green-50 border-green-200 text-green-800': variant === 'success',
'bg-yellow-50 border-yellow-200 text-yellow-800': variant === 'warning',
}
)}
>
{children}
</div>
);
}
// 반응형 그리드
function Gallery() {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3
lg:grid-cols-4 gap-4 p-4">
{/* 아이템들 */}
</div>
);
}
Tailwind 설정 커스터마이징
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
brand: {
50: '#eff6ff',
500: '#3b82f6',
900: '#1e3a5f',
},
},
fontFamily: {
sans: ['Pretendard', 'sans-serif'],
},
spacing: {
18: '4.5rem',
},
},
},
};
장단점
장점
- 빠른 개발 속도 (파일 전환 없이 바로 스타일 적용)
- 디자인 시스템 일관성 (사전 정의된 스케일)
- 빌드 시 미사용 CSS 제거 (매우 작은 번들)
- 런타임 오버헤드 없음
- 반응형 작성이 직관적 (
sm:,md:,lg:접두사) - 다크 모드 지원 (
dark:접두사)
단점
- HTML(JSX)이 클래스명으로 복잡해질 수 있음
- 초기 학습 곡선 (클래스 이름 암기 필요)
- 고도로 동적인 스타일은 설정이 번거로움
- 디자인 팀과의 협업 시 익숙하지 않을 수 있음
세 방식 종합 비교
| 기준 | CSS-in-JS | CSS Modules | Tailwind CSS |
|---|---|---|---|
| 스코프 | 자동 (런타임) | 자동 (빌드) | 없음 (전역이나 충돌 적음) |
| 동적 스타일 | 매우 쉬움 | 번거로움 | 제한적 |
| 성능 | 런타임 비용 있음 | 없음 | 없음 |
| 번들 크기 | 큼 (라이브러리 포함) | 작음 | 매우 작음 |
| 학습 곡선 | 중간 | 낮음 | 중간 |
| 유지보수 | 좋음 (컴포넌트 단위) | 좋음 | 파일 분리 없음 |
| TypeScript | 완벽 지원 | 추가 설정 필요 | 자동완성 플러그인 필요 |
| SSR 호환 | 설정 필요 | 완벽 | 완벽 |
| 디자인 시스템 | 자체 구축 | 자체 구축 | 내장 |
선택 기준
CSS-in-JS를 선택할 때
- 테마 전환이 자주 일어나는 앱
- props에 따른 복잡한 동적 스타일이 필요한 경우
- 컴포넌트 라이브러리 개발
CSS Modules를 선택할 때
- 기존 CSS 지식을 그대로 활용하고 싶을 때
- 성능이 최우선이고 런타임 비용을 허용하지 않을 때
- React Server Components를 적극 활용하는 Next.js 13+ 프로젝트
Tailwind CSS를 선택할 때
- 빠른 프로토타이핑이나 스타트업 환경
- 일관된 디자인 시스템이 필요한 팀
- 유틸리티 클래스 방식에 익숙한 팀
실무에서는 단일 방식만 고집하기보다, 전체 레이아웃은 Tailwind나 CSS Modules로, 복잡한 인터랙티브 컴포넌트는 CSS-in-JS로 혼합하는 경우도 많습니다.