Tailwind CSS 완벽 가이드 | 설치·사용법·커스터마이징·반응형·다크모드

Tailwind CSS 완벽 가이드 | 설치·사용법·커스터마이징·반응형·다크모드

이 글의 핵심

Tailwind CSS를 실무에 활용하는 완벽 가이드입니다. 설치부터 유틸리티 클래스, 커스터마이징, 반응형 디자인, 다크모드, 성능 최적화까지 실전 예제로 정리했습니다.

실무 경험 공유: 대규모 스트리밍 플랫폼의 관리자 대시보드를 Tailwind CSS로 리뉴얼하면서, 기존 10,000줄의 CSS를 3,000줄로 줄이고 빌드 시간을 70% 단축한 경험을 바탕으로 작성했습니다.

들어가며: “CSS 작성이 너무 번거로워요”

실무 문제 시나리오

시나리오 1: 클래스 이름 짓기가 힘들어요
.button-primary-large-rounded-blue처럼 긴 클래스 이름을 매번 고민합니다. Tailwind는 유틸리티 클래스로 즉시 스타일링할 수 있습니다.

시나리오 2: CSS 파일이 너무 커져요
프로젝트가 커질수록 CSS 파일이 수천 줄이 됩니다. Tailwind는 사용하지 않는 스타일을 자동으로 제거합니다.

시나리오 3: 일관성 없는 디자인
개발자마다 margin: 15px, margin: 20px처럼 제각각입니다. Tailwind는 디자인 시스템을 강제합니다.

flowchart LR
    subgraph Before["기존 CSS"]
        A1[클래스 이름 고민]
        A2[CSS 파일 비대화]
        A3[일관성 부족]
    end
    subgraph After["Tailwind CSS"]
        B1[유틸리티 클래스]
        B2[자동 최적화]
        B3[디자인 시스템]
    end
    Before --> After

1. Tailwind CSS 시작하기

설치 (Next.js)

npx create-next-app@latest my-app
cd my-app
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

설치 (Vite + React)

npm create vite@latest my-app -- --template react
cd my-app
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

첫 번째 컴포넌트

export default function Button() {
  return (
    <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
      클릭하세요
    </button>
  );
}

2. 핵심 유틸리티 클래스

레이아웃

// Flexbox
<div className="flex items-center justify-between">
  <div>왼쪽</div>
  <div>오른쪽</div>
</div>

// Grid
<div className="grid grid-cols-3 gap-4">
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>

// 중앙 정렬
<div className="flex items-center justify-center h-screen">
  <div>중앙</div>
</div>

간격 (Spacing)

// Margin
<div className="m-4">margin: 1rem</div>
<div className="mx-4">margin-left/right: 1rem</div>
<div className="mt-8">margin-top: 2rem</div>

// Padding
<div className="p-4">padding: 1rem</div>
<div className="px-6 py-3">padding-x: 1.5rem, padding-y: 0.75rem</div>

// 간격 스케일
// 0, 0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64

타이포그래피

// 폰트 크기
<h1 className="text-4xl font-bold">제목</h1>
<p className="text-base">본문</p>
<small className="text-sm text-gray-500">작은 텍스트</small>

// 폰트 굵기
<p className="font-light">Light (300)</p>
<p className="font-normal">Normal (400)</p>
<p className="font-bold">Bold (700)</p>

// 텍스트 정렬
<p className="text-left">왼쪽</p>
<p className="text-center">중앙</p>
<p className="text-right">오른쪽</p>

// 줄 높이
<p className="leading-tight">좁은 줄 간격</p>
<p className="leading-relaxed">넓은 줄 간격</p>

색상

// 배경색
<div className="bg-blue-500">파란색</div>
<div className="bg-red-600">빨간색</div>
<div className="bg-gray-100">회색</div>

// 텍스트 색상
<p className="text-blue-500">파란 텍스트</p>
<p className="text-red-600">빨간 텍스트</p>

// 색상 스케일: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900

테두리 및 그림자

// 테두리
<div className="border border-gray-300 rounded">기본 테두리</div>
<div className="border-2 border-blue-500 rounded-lg">두꺼운 테두리</div>
<div className="rounded-full">원형</div>

// 그림자
<div className="shadow-sm">작은 그림자</div>
<div className="shadow-md">중간 그림자</div>
<div className="shadow-lg">큰 그림자</div>
<div className="shadow-xl">매우 큰 그림자</div>

3. 반응형 디자인

브레이크포인트

접두사최소 너비CSS
sm:640px@media (min-width: 640px)
md:768px@media (min-width: 768px)
lg:1024px@media (min-width: 1024px)
xl:1280px@media (min-width: 1280px)
2xl:1536px@media (min-width: 1536px)

모바일 퍼스트

// 모바일: 세로, 태블릿 이상: 가로
<div className="flex flex-col md:flex-row">
  <div className="w-full md:w-1/2">왼쪽</div>
  <div className="w-full md:w-1/2">오른쪽</div>
</div>

// 모바일: 1열, 태블릿: 2열, 데스크톱: 3열
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>

// 모바일: 숨김, 데스크톱: 표시
<div className="hidden lg:block">
  데스크톱에서만 보임
</div>

실전 예제: 반응형 네비게이션

export default function Navbar() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <nav className="bg-white shadow-lg">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex justify-between h-16">
          {/* 로고 */}
          <div className="flex items-center">
            <h1 className="text-xl font-bold">로고</h1>
          </div>

          {/* 데스크톱 메뉴 */}
          <div className="hidden md:flex items-center space-x-8">
            <a href="#" className="text-gray-700 hover:text-blue-500">홈</a>
            <a href="#" className="text-gray-700 hover:text-blue-500">서비스</a>
            <a href="#" className="text-gray-700 hover:text-blue-500">문의</a>
          </div>

          {/* 모바일 메뉴 버튼 */}
          <div className="md:hidden flex items-center">
            <button onClick={() => setIsOpen(!isOpen)}>
              <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
              </svg>
            </button>
          </div>
        </div>
      </div>

      {/* 모바일 메뉴 */}
      {isOpen && (
        <div className="md:hidden">
          <div className="px-2 pt-2 pb-3 space-y-1">
            <a href="#" className="block px-3 py-2 text-gray-700 hover:bg-gray-100 rounded">홈</a>
            <a href="#" className="block px-3 py-2 text-gray-700 hover:bg-gray-100 rounded">서비스</a>
            <a href="#" className="block px-3 py-2 text-gray-700 hover:bg-gray-100 rounded">문의</a>
          </div>
        </div>
      )}
    </nav>
  );
}

4. 다크 모드

설정

// tailwind.config.js
module.exports = {
  darkMode: 'class', // 또는 'media'
  // ...
}

사용법

// 클래스 기반 다크 모드
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
  <h1 className="text-2xl">제목</h1>
  <p className="text-gray-600 dark:text-gray-300">본문</p>
</div>

다크 모드 토글 구현

'use client';

import { useEffect, useState } from 'react';

export default function DarkModeToggle() {
  const [isDark, setIsDark] = useState(false);

  useEffect(() => {
    // 로컬 스토리지에서 테마 불러오기
    const theme = localStorage.getItem('theme');
    if (theme === 'dark') {
      setIsDark(true);
      document.documentElement.classList.add('dark');
    }
  }, []);

  const toggleDarkMode = () => {
    if (isDark) {
      document.documentElement.classList.remove('dark');
      localStorage.setItem('theme', 'light');
      setIsDark(false);
    } else {
      document.documentElement.classList.add('dark');
      localStorage.setItem('theme', 'dark');
      setIsDark(true);
    }
  };

  return (
    <button
      onClick={toggleDarkMode}
      className="p-2 rounded-lg bg-gray-200 dark:bg-gray-700"
    >
      {isDark ? '🌞' : '🌙'}
    </button>
  );
}

5. 커스터마이징

색상 추가

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        'brand': {
          50: '#f0f9ff',
          100: '#e0f2fe',
          500: '#0ea5e9',
          900: '#0c4a6e',
        },
        'primary': '#3b82f6',
        'secondary': '#8b5cf6',
      },
    },
  },
}
<div className="bg-brand-500 text-white">브랜드 색상</div>
<button className="bg-primary hover:bg-blue-600">버튼</button>

폰트 추가

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontFamily: {
        'sans': ['Pretendard', 'system-ui', 'sans-serif'],
        'display': ['Montserrat', 'sans-serif'],
      },
    },
  },
}
/* globals.css */
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@700&display=swap');
<h1 className="font-display text-4xl">Display Font</h1>
<p className="font-sans">본문 폰트</p>

간격 추가

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      spacing: {
        '128': '32rem',
        '144': '36rem',
      },
    },
  },
}
<div className="mt-128">큰 여백</div>

애니메이션 추가

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      animation: {
        'fade-in': 'fadeIn 0.5s ease-in',
        'slide-up': 'slideUp 0.3s ease-out',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { transform: 'translateY(20px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
      },
    },
  },
}
<div className="animate-fade-in">페이드 인</div>
<div className="animate-slide-up">슬라이드 업</div>

6. 컴포넌트 패턴

@apply 지시어

/* components.css */
@layer components {
  .btn-primary {
    @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
  }

  .card {
    @apply bg-white rounded-lg shadow-md p-6;
  }

  .input-field {
    @apply border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500;
  }
}
<button className="btn-primary">버튼</button>
<div className="card">카드</div>
<input className="input-field" />

재사용 가능한 컴포넌트

// components/Button.jsx
export default function Button({ 
  children, 
  variant = 'primary', 
  size = 'md',
  ...props 
}) {
  const baseClasses = 'font-bold rounded transition-colors';
  
  const variants = {
    primary: 'bg-blue-500 hover:bg-blue-700 text-white',
    secondary: 'bg-gray-500 hover:bg-gray-700 text-white',
    outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-50',
  };
  
  const sizes = {
    sm: 'py-1 px-2 text-sm',
    md: 'py-2 px-4',
    lg: 'py-3 px-6 text-lg',
  };
  
  return (
    <button 
      className={`${baseClasses} ${variants[variant]} ${sizes[size]}`}
      {...props}
    >
      {children}
    </button>
  );
}
<Button variant="primary" size="md">기본 버튼</Button>
<Button variant="outline" size="lg">아웃라인 버튼</Button>

7. 실전 예제: 랜딩 페이지

export default function LandingPage() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
      {/* Hero Section */}
      <section className="container mx-auto px-4 py-20">
        <div className="max-w-4xl mx-auto text-center">
          <h1 className="text-5xl md:text-6xl font-bold text-gray-900 mb-6">
            당신의 비즈니스를
            <span className="text-blue-600"> 성장</span>시키세요
          </h1>
          <p className="text-xl text-gray-600 mb-8">
            최고의 솔루션으로 생산성을 2배 향상시킬 수 있습니다
          </p>
          <div className="flex flex-col sm:flex-row gap-4 justify-center">
            <button className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg transition-colors">
              무료 시작하기
            </button>
            <button className="border-2 border-blue-600 text-blue-600 hover:bg-blue-50 font-bold py-3 px-8 rounded-lg transition-colors">
              데모 보기
            </button>
          </div>
        </div>
      </section>

      {/* Features Section */}
      <section className="container mx-auto px-4 py-20">
        <h2 className="text-3xl font-bold text-center mb-12">주요 기능</h2>
        <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
          {[
            { icon: '⚡', title: '빠른 속도', desc: '초고속 처리 성능' },
            { icon: '🔒', title: '보안', desc: '엔터프라이즈급 보안' },
            { icon: '📊', title: '분석', desc: '실시간 데이터 분석' },
          ].map((feature, i) => (
            <div key={i} className="bg-white rounded-lg shadow-lg p-8 hover:shadow-xl transition-shadow">
              <div className="text-4xl mb-4">{feature.icon}</div>
              <h3 className="text-xl font-bold mb-2">{feature.title}</h3>
              <p className="text-gray-600">{feature.desc}</p>
            </div>
          ))}
        </div>
      </section>

      {/* CTA Section */}
      <section className="bg-blue-600 py-20">
        <div className="container mx-auto px-4 text-center">
          <h2 className="text-3xl font-bold text-white mb-6">
            지금 바로 시작하세요
          </h2>
          <p className="text-xl text-blue-100 mb-8">
            14일 무료 체험, 신용카드 불필요
          </p>
          <button className="bg-white text-blue-600 hover:bg-gray-100 font-bold py-3 px-8 rounded-lg transition-colors">
            무료로 시작하기
          </button>
        </div>
      </section>
    </div>
  );
}

8. 성능 최적화

PurgeCSS (자동)

Tailwind는 프로덕션 빌드 시 사용하지 않는 CSS를 자동으로 제거합니다.

// tailwind.config.js
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
  ],
  // 사용된 클래스만 최종 CSS에 포함
}

JIT (Just-In-Time) 모드

Tailwind 3.0부터 JIT가 기본값입니다.

// tailwind.config.js
module.exports = {
  mode: 'jit', // 기본값
  // ...
}

장점:

  • 개발 시 빌드 속도 향상
  • 임의 값 사용 가능: w-[137px], top-[-113px]
  • 모든 변형 조합 가능

임의 값 사용

// JIT 모드에서 가능
<div className="w-[137px] h-[42px]">커스텀 크기</div>
<div className="top-[-113px]">커스텀 위치</div>
<div className="bg-[#1da1f2]">커스텀 색상</div>

9. 플러그인

공식 플러그인

npm install -D @tailwindcss/forms @tailwindcss/typography @tailwindcss/aspect-ratio
// tailwind.config.js
module.exports = {
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/aspect-ratio'),
  ],
}

@tailwindcss/forms

// 기본 폼 스타일 자동 적용
<input type="text" className="rounded-md" />
<select className="rounded-md">
  <option>옵션 1</option>
</select>
<textarea className="rounded-md"></textarea>

@tailwindcss/typography

// 마크다운 콘텐츠 스타일링
<article className="prose lg:prose-xl">
  <h1>제목</h1>
  <p>본문 텍스트...</p>
  <ul>
    <li>항목 1</li>
    <li>항목 2</li>
  </ul>
</article>

10. 자주 하는 실수와 해결법

문제 1: 클래스가 적용 안 됨

// ❌ 잘못된 코드 - 동적 클래스명
const color = 'blue';
<div className={`bg-${color}-500`}>텍스트</div>

// ✅ 올바른 코드 - 전체 클래스명
const colorClasses = {
  blue: 'bg-blue-500',
  red: 'bg-red-500',
};
<div className={colorClasses[color]}>텍스트</div>

문제 2: 스타일 우선순위

// ❌ 잘못된 코드 - 나중 클래스가 우선하지 않음
<div className="text-red-500 text-blue-500">
  파란색이 아니라 빨간색!
</div>

// ✅ 올바른 코드 - 조건부 렌더링
<div className={isActive ? 'text-blue-500' : 'text-red-500'}>
  텍스트
</div>

문제 3: 반응형 클래스 순서

// ❌ 잘못된 코드
<div className="lg:text-xl md:text-lg text-base">
  순서 상관없음 (모바일 퍼스트)
</div>

// ✅ 올바른 코드 (가독성)
<div className="text-base md:text-lg lg:text-xl">
  작은 화면부터 큰 화면 순서
</div>

정리 및 체크리스트

핵심 요약

  • 유틸리티 퍼스트: 클래스 이름 고민 없이 즉시 스타일링
  • 반응형 디자인: 모바일 퍼스트, 브레이크포인트 접두사
  • 다크 모드: dark: 접두사로 간단 구현
  • 커스터마이징: tailwind.config.js로 디자인 시스템 구축
  • 성능 최적화: 사용하지 않는 CSS 자동 제거

실무 체크리스트

  • Tailwind CSS 설치 및 설정
  • content 경로 올바르게 설정
  • 디자인 시스템 커스터마이징 (색상, 폰트 등)
  • 다크 모드 구현
  • 반응형 디자인 테스트
  • 재사용 컴포넌트 작성
  • 프로덕션 빌드 최적화 확인

같이 보면 좋은 글

  • Next.js 15 완벽 가이드 | App Router·Server Actions·Turbopack
  • React 18 완벽 가이드 | Concurrent Rendering·Suspense
  • ChatGPT API 완벽 가이드 | 사용법·요금·프롬프트

이 글에서 다루는 키워드

Tailwind CSS, CSS, 유틸리티 클래스, 반응형, 다크모드, Frontend, Web, UI

자주 묻는 질문 (FAQ)

Q. Tailwind CSS vs 일반 CSS, 어떤 게 나은가요?

A. 빠른 개발과 일관성이 중요하면 Tailwind를 권장합니다. 복잡한 애니메이션이나 특수한 스타일은 일반 CSS가 나을 수 있습니다.

Q. 클래스명이 너무 길어지는데요?

A. @apply 지시어로 재사용 가능한 클래스를 만들거나, 컴포넌트로 추상화하세요.

Q. 번들 크기가 커지지 않나요?

A. 프로덕션 빌드 시 사용하지 않는 CSS가 자동으로 제거되어 최종 파일은 매우 작습니다 (보통 10KB 이하).

Q. 기존 CSS와 함께 사용할 수 있나요?

A. 네, Tailwind는 기존 CSS와 함께 사용할 수 있습니다. 점진적으로 마이그레이션할 수 있습니다.

---
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3