본문으로 건너뛰기
Previous
Next
Tailwind CSS Complete Guide

Tailwind CSS Complete Guide

Tailwind CSS Complete Guide

이 글의 핵심

Tailwind CSS replaces custom CSS with composable utility classes, enabling rapid UI development without leaving your HTML. This guide covers the full workflow — from setup to design systems and Tailwind v4.

What This Guide Covers

Tailwind CSS is the most popular utility-first CSS framework. Instead of writing custom CSS, you compose small utility classes directly in your HTML. This guide covers the core concepts, responsive design, theming, and Tailwind v4 changes.

Real-world insight: Switching from BEM + custom CSS to Tailwind reduced the CSS bundle by 70% and eliminated every naming conflict we’d been fighting for months.


Installation (Tailwind v4)

npm install tailwindcss @tailwindcss/vite
// vite.config.js
import tailwindcss from '@tailwindcss/vite';

export default {
  plugins: [tailwindcss()],
};
/* src/index.css */
@import "tailwindcss";

That’s it — no tailwind.config.js needed for basic setups.


1. Core Utility Classes

Spacing

<div class="m-4">          <!-- margin: 1rem -->
<div class="mx-auto">      <!-- margin-left/right: auto -->
<div class="p-6">          <!-- padding: 1.5rem -->
<div class="px-4 py-2">    <!-- horizontal + vertical padding -->
<div class="mt-8 mb-4">    <!-- top + bottom margin -->

Typography

<p class="text-sm">         <!-- font-size: 0.875rem -->
<p class="text-xl">         <!-- font-size: 1.25rem -->
<p class="font-bold">       <!-- font-weight: 700 -->
<p class="text-gray-600">   <!-- color: #4b5563 -->
<p class="leading-relaxed"> <!-- line-height: 1.625 -->
<p class="tracking-wide">   <!-- letter-spacing: 0.025em -->
<p class="uppercase">       <!-- text-transform: uppercase -->
<p class="truncate">        <!-- overflow: hidden + ellipsis -->

Colors

<div class="bg-blue-500 text-white">    <!-- background + text color -->
<div class="border border-gray-200">   <!-- border -->
<div class="text-red-600">             <!-- error text -->

Colors follow a 50–950 scale: gray-100 is lightest, gray-900 is darkest.


2. Responsive Design

Tailwind uses mobile-first breakpoints with prefixes:

PrefixMin-width
(none)0px
sm:640px
md:768px
lg:1024px
xl:1280px
2xl:1536px
<!-- 1 column on mobile, 2 on tablet, 3 on desktop -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  <div class="p-4 bg-white rounded-lg shadow">Card 1</div>
  <div class="p-4 bg-white rounded-lg shadow">Card 2</div>
  <div class="p-4 bg-white rounded-lg shadow">Card 3</div>
</div>

<!-- Hidden on mobile, visible on desktop -->
<nav class="hidden lg:flex gap-6">
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>

<!-- Full width on mobile, fixed width on desktop -->
<div class="w-full md:w-96">
  Sidebar
</div>

3. Flexbox and Grid

<!-- Flexbox row, centered, spaced -->
<div class="flex items-center justify-between gap-4">
  <span>Left</span>
  <span>Right</span>
</div>

<!-- Flex column, center everything -->
<div class="flex flex-col items-center justify-center min-h-screen">
  <h1>Centered content</h1>
</div>

<!-- CSS Grid, auto-fill columns -->
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
  <!-- Auto-responsive grid -->
</div>

4. State Variants

<!-- Hover -->
<button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
  Hover me
</button>

<!-- Focus -->
<input class="border focus:outline-none focus:ring-2 focus:ring-blue-500 rounded px-3 py-2" />

<!-- Active -->
<button class="bg-gray-100 active:bg-gray-200 px-4 py-2">Click</button>

<!-- Disabled -->
<button class="bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
  Disabled
</button>

<!-- Group hover (parent hover affects child) -->
<div class="group flex items-center gap-2 cursor-pointer">
  <span>Icon</span>
  <span class="opacity-0 group-hover:opacity-100 transition">Details</span>
</div>

5. Dark Mode

<!-- Toggle dark mode via .dark class on <html> -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  <h1 class="text-2xl font-bold">Supports dark mode</h1>
</div>
/* In CSS (v4), configure dark mode variant if needed */
@variant dark (&:where(.dark, .dark *));
// Toggle dark mode programmatically
document.documentElement.classList.toggle('dark');

6. Custom Theme (Tailwind v4)

In v4, theming moves to CSS with @theme:

@import "tailwindcss";

@theme {
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a5f;

  --font-sans: 'Inter', sans-serif;
  --font-mono: 'JetBrains Mono', monospace;

  --radius-xl: 1rem;

  --spacing-18: 4.5rem;
}
<!-- Use custom tokens -->
<div class="bg-brand-500 text-brand-50 rounded-xl p-18">
  Custom themed card
</div>

7. Component Patterns

Button component (React)

type ButtonProps = {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

const variants = {
  primary: 'bg-blue-600 hover:bg-blue-700 text-white',
  secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-900',
  danger: 'bg-red-600 hover:bg-red-700 text-white',
};

const sizes = {
  sm: 'px-3 py-1.5 text-sm',
  md: 'px-4 py-2 text-base',
  lg: 'px-6 py-3 text-lg',
};

export function Button({ variant = 'primary', size = 'md', className = '', ...props }: ButtonProps) {
  return (
    <button
      className={`
        ${variants[variant]}
        ${sizes[size]}
        rounded-lg font-medium transition-colors
        focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
        disabled:opacity-50 disabled:cursor-not-allowed
        ${className}
      `}
      {...props}
    />
  );
}

Card component

<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-sm border border-gray-100 dark:border-gray-700 p-6 hover:shadow-md transition-shadow">
  <img class="w-full h-48 object-cover rounded-xl mb-4" src="..." alt="..." />
  <h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Card Title</h2>
  <p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">Description text</p>
</div>

8. Animations and Transitions

<!-- Built-in transitions -->
<div class="transition-all duration-200 ease-in-out">...</div>

<!-- Transform on hover -->
<div class="hover:scale-105 hover:-translate-y-1 transition-transform duration-200">Card</div>

<!-- Tailwind animations -->
<div class="animate-spin">⟳</div>       <!-- spinning loader -->
<div class="animate-pulse bg-gray-200 rounded h-4 w-32"></div>  <!-- skeleton -->
<div class="animate-bounce">↓</div>

9. Using cn() for Conditional Classes

Install clsx + tailwind-merge:

npm install clsx tailwind-merge
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs) {
  return twMerge(clsx(inputs));
}

// Usage
<div className={cn(
  'rounded-lg p-4',
  isActive && 'bg-blue-100 border-blue-500',
  isError && 'bg-red-100 border-red-500',
  className  // allow override from parent
)} />

twMerge resolves conflicts when the same property appears in multiple classes (e.g., p-4 vs p-6).


Deep dive: JIT engine, PostCSS, content scanning, tokens

Memorizing utilities is only half the story. Understanding how the build turns class names into CSS explains issues like “works locally, wrong in production” and “dynamic classes disappear after deploy.” The model below applies to both v3-style tailwind.config + PostCSS stacks and v4 @import "tailwindcss" setups (often paired with Vite and Lightning CSS).

JIT (Just-In-Time) compilation

Tailwind’s default mode is JIT: at build time it walks the files matched by content (v3) or the file set your bundler feeds in, extracts strings that look like Tailwind class candidates, and emits only the CSS rules needed for those candidates. It is not a runtime engine in the browser that assembles utilities on the fly. That matters because string concatenation such as 'text-' + color may never appear as a complete class name to the scanner, so no rule is generated—use a finite map of full class names, safelist, or a design-system enum.

In development, incremental rebuilds keep feedback fast; in production you get a single deterministic stylesheet from the same pipeline.

PostCSS plugin architecture

In classic PostCSS setups, Tailwind runs as a PostCSS plugin: CSS is parsed to an AST, plugins run in order, then the tree is serialized back to text. Tailwind expands @tailwind directives, resolves @apply / @layer against your theme and candidate list, and hands off to tools like Autoprefixer for vendor prefixes. Order matters—run Tailwind before generic downstream transforms that assume “final” declarations, unless your toolchain docs say otherwise. In v4, first-party Vite integration may bypass a manual postcss.config, but the mental model stays: scan → generate → emit, with optional Lightning CSS minification.

“Purge” vs content-driven generation and tree-shaking

Older workflows used PurgeCSS-style delete unused rules from a huge prebuilt file. Modern Tailwind instead does not emit unused utilities in the first place (for statically discoverable classes). The analogy to JS tree-shaking is imperfect, but the outcome is similar: small final CSS when your content globs cover every template that can introduce classes.

Treat content as a whitelist of scan roots. If a class only appears under a path you forgot—shared UI packages, Storybook-only files, rarely built apps in a monorepo—that utility may be missing in production. Prefer the vocabulary content scanning and candidate generation over vague “purge” when onboarding teammates.

Design token system

Primitive tokens (palette steps, spacing scale) map cleanly to theme / theme.extend (v3) or @theme (v4) as build-time constants. Semantic tokens (surface, danger) help you swap themes without sprinkling raw palette steps everywhere. When values must change after deploy (multi-tenant branding), CSS custom properties as the source of truth with Tailwind bridging via bg-[var(--color-surface)] is a common production pattern—document fallbacks and scope (:root vs [data-theme]).

Production patterns checklist

  • Include package sources in content for internal UI libraries.
  • Safelist or explicit class maps for CMS-driven or dynamically chosen classes.
  • tailwind-merge + clsx (or cn) to resolve conflicting utilities in conditional UIs.
  • Share presets from a design-system package, but still merge its paths into the app content.
  • Run the same build in CI as locally so cwd and env-based path differences do not hide scan gaps.

For a longer treatment of tokens, layers, and library boundaries, see [Tailwind CSS: Components, Tokens, and a Practical Design System](/en/blog/tailwind-css-component-design-system/.


Key Takeaways

ConceptHow
Spacingm-, p-, gap- with 4px scale (1 unit = 4px)
ResponsiveAdd sm:, md:, lg: prefix to any class
Dark modeAdd dark: prefix; toggle .dark on <html>
Hover/FocusAdd hover:, focus:, active: prefix
Custom theme@theme in CSS (v4) or tailwind.config.js (v3)
ConditionalUse cn() = clsx + twMerge

Tailwind’s learning curve is the class names — after a few days they become muscle memory. The payoff is a consistent design system with zero naming conflicts and tiny production CSS.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Master Tailwind CSS from basics to advanced patterns. Covers responsive design, dark mode, custom themes, component patt… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • [React 18 Deep Dive | Concurrent Features· Suspense](/en/blog/react-18-deep-dive/
  • [Next.js App Router: SSR, SSG, and ISR](/en/blog/nextjs-app-router-ssr-ssg-isr/

이 글에서 다루는 키워드 (관련 검색어)

Tailwind CSS, CSS, Frontend, Design, React, UI 등으로 검색하시면 이 글이 도움이 됩니다.