전체 목록
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-componentsEmotion이 있습니다.

기본 사용법 (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-JSCSS ModulesTailwind CSS
스코프자동 (런타임)자동 (빌드)없음 (전역이나 충돌 적음)
동적 스타일매우 쉬움번거로움제한적
성능런타임 비용 있음없음없음
번들 크기큼 (라이브러리 포함)작음매우 작음
학습 곡선중간낮음중간
유지보수좋음 (컴포넌트 단위)좋음파일 분리 없음
TypeScript완벽 지원추가 설정 필요자동완성 플러그인 필요
SSR 호환설정 필요완벽완벽
디자인 시스템자체 구축자체 구축내장

선택 기준

CSS-in-JS를 선택할 때

  • 테마 전환이 자주 일어나는 앱
  • props에 따른 복잡한 동적 스타일이 필요한 경우
  • 컴포넌트 라이브러리 개발

CSS Modules를 선택할 때

  • 기존 CSS 지식을 그대로 활용하고 싶을 때
  • 성능이 최우선이고 런타임 비용을 허용하지 않을 때
  • React Server Components를 적극 활용하는 Next.js 13+ 프로젝트

Tailwind CSS를 선택할 때

  • 빠른 프로토타이핑이나 스타트업 환경
  • 일관된 디자인 시스템이 필요한 팀
  • 유틸리티 클래스 방식에 익숙한 팀

실무에서는 단일 방식만 고집하기보다, 전체 레이아웃은 Tailwind나 CSS Modules로, 복잡한 인터랙티브 컴포넌트는 CSS-in-JS로 혼합하는 경우도 많습니다.