DevToolBox무료
블로그

Tailwind CSS 가이드: 유틸리티 클래스, 다크 모드, v4, React/Next.js 통합

13분 읽기by DevToolBox

Tailwind CSS Complete Guide: Utility-First CSS, JIT, v4, and React Integration (2025)

A comprehensive Tailwind CSS guide covering utility-first principles, JIT compiler, responsive prefixes, dark mode, custom configuration, Flexbox and Grid utilities, Tailwind v4 changes, React/Next.js integration with cn() and shadcn/ui, and a full comparison with Bootstrap, CSS Modules, and Styled Components.

TL;DR

Tailwind CSS is a utility-first CSS framework that lets you build designs directly in HTML/JSX by composing small, single-purpose utility classes. The JIT (Just-In-Time) compiler generates only the CSS you actually use, resulting in tiny production bundles. Responsive design is handled with mobile-first breakpoint prefixes (sm:, md:, lg:, xl:). Dark mode works with the dark: prefix. Tailwind v4 (2025) switches to a CSS-first configuration model — no more tailwind.config.js. For React/Next.js, the cn() utility from clsx+tailwind-merge is essential for conditional class merging. Tailwind is best for teams that want rapid UI development with a consistent design system without writing custom CSS.

Key Takeaways

  • Tailwind is utility-first — you style elements by composing small single-purpose classes like flex, pt-4, text-center, and rotate-90 directly in HTML/JSX, rather than writing custom CSS.
  • The JIT (Just-In-Time) compiler scans your source files and generates only the CSS for classes you actually use — production bundles are typically under 10KB even for large apps.
  • Responsive design uses mobile-first breakpoint prefixes: sm: (640px), md: (768px), lg: (1024px), xl: (1280px), 2xl: (1536px). Unprefixed utilities apply to all screen sizes.
  • Dark mode is supported natively with the dark: variant prefix. You can configure class-based (class="dark" on html) or media-based (prefers-color-scheme) strategy.
  • Tailwind v4 (2025) introduces CSS-first configuration: import Tailwind in CSS, define your theme as CSS custom properties, and eliminate tailwind.config.js entirely.
  • For React/Next.js, use cn() from clsx + tailwind-merge to safely merge conditional classes, and consider shadcn/ui or cva for building reusable typed component variants.

Tailwind CSS has fundamentally changed how developers write CSS. Instead of crafting hand-rolled stylesheets or fighting framework overrides, you compose designs from hundreds of low-level utility classes directly in your HTML or JSX. Created by Adam Wathan and released in 2017, Tailwind CSS now powers the UIs of companies like GitHub, Shopify, OpenAI, and hundreds of thousands of projects worldwide. With the release of Tailwind CSS v4 in early 2025, the framework has evolved dramatically — moving to a CSS-first configuration model, introducing a faster Rust-based engine, and simplifying the developer experience further. This guide is your complete reference for mastering Tailwind CSS in 2025, covering everything from the core utility-first philosophy to advanced React integration patterns, custom configuration, and a detailed comparison with alternative CSS approaches.

1. What Is Tailwind CSS? The Utility-First Philosophy

Tailwind CSS is a utility-first CSS framework. Instead of providing pre-built components like Bootstrap's .btn or .card, Tailwind gives you thousands of small, single-purpose utility classes — each mapping directly to a specific CSS property and value. To build a UI, you compose these classes directly in your markup. This is a fundamentally different mental model: styles are co-located with the HTML element they affect, there are no class naming decisions to make, and every utility is constrained to a design system scale.

/* Traditional CSS approach */
.card {
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  background-color: #ffffff;
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  max-width: 24rem;
}

/* Tailwind CSS utility-first approach */
<div class="flex flex-col p-6 bg-white rounded-lg shadow-sm max-w-sm">
  ...
</div>

<!-- No CSS file needed — styles live in the markup -->

How Tailwind Handles Unused CSS — The JIT Compiler

The most common question about Tailwind is: "Won't all those classes make a huge CSS file?" The answer is no — and the JIT (Just-In-Time) compiler is why. Tailwind scans your configured content files (HTML, JSX, TSX, Vue, etc.) and generates CSS only for the classes you actually use. A typical production bundle is 5–15KB — smaller than Bootstrap's minified CSS which is ~22KB before gzip. With JIT, you also unlock arbitrary value support: use any value with square bracket syntax like w-[237px], text-[#1a73e8], or top-[117px] without configuring anything.

<!-- JIT Arbitrary values — any CSS value with square brackets -->
<div class="w-[237px] h-[117px] bg-[#1a73e8] top-[117px]">
  Exact pixel control when needed
</div>

<!-- Arbitrary properties — full CSS escape hatch -->
<div class="[mask-image:linear-gradient(to_bottom,black,transparent)]">
  Fade out effect
</div>

<!-- Opacity modifier (slash syntax) -->
<div class="bg-blue-500/50 text-white/80">
  50% opacity background, 80% opacity text
</div>

2. Core Concepts: Spacing, Colors, Typography, and Responsive Prefixes

Spacing Scale

Tailwind uses a 4px-base spacing scale. The number after the prefix multiplies by 4px: p-1 = 4px, p-2 = 8px, p-4 = 16px, p-8 = 32px, p-16 = 64px. This scale applies to padding (p-, px-, py-, pt-, pr-, pb-, pl-), margin (m-, mx-, my-, mt-, mr-, mb-, ml-), width (w-), height (h-), gap, and more. For values outside the scale, use arbitrary values: p-[13px], mt-[3.25rem]. Negative margins are supported with a minus prefix: -mt-4.

<!-- Padding: p-{n} = n * 4px -->
<div class="p-4">       /* padding: 16px all sides */
<div class="px-6 py-3"> /* padding: 12px top/bottom, 24px left/right */
<div class="pt-2 pb-8"> /* padding-top: 8px, padding-bottom: 32px */

<!-- Margin -->
<div class="mx-auto">   /* margin: 0 auto (centering) */
<div class="mt-4 mb-8"> /* margin-top: 16px, margin-bottom: 32px */
<div class="-mt-4">     /* negative margin: margin-top: -16px */

<!-- Arbitrary values -->
<div class="p-[13px] mt-[3.25rem]"> /* exact values */
<div class="w-[calc(100%-2rem)]">    /* CSS calc() support */

Color Palette

Tailwind ships with a curated palette of 22 colors, each with 11 shades (50 through 950). You apply colors to text (text-blue-500), background (bg-gray-100), border (border-red-300), and more. Use the opacity modifier for transparent variants: bg-blue-500/50 for 50% opacity, text-black/80 for 80% opacity. For custom colors, use arbitrary values: text-[#1a73e8] or bg-[rgb(59,130,246)].

<!-- Named palette colors (50 through 950 shades) -->
<p class="text-blue-500">            /* color: #3b82f6 */
<div class="bg-gray-100">            /* background: #f3f4f6 */
<div class="border border-red-300">  /* border: 1px solid #fca5a5 */
<div class="text-emerald-600">       /* color: #059669 */

<!-- Opacity modifier (slash) -->
<div class="bg-blue-500/50">         /* opacity: 0.5 */
<div class="bg-black/10">            /* black at 10% opacity */

<!-- Arbitrary color values -->
<p class="text-[#1a73e8]">           /* exact hex */
<div class="bg-[rgb(59,130,246)]">   /* rgb values */
<div class="border-[hsl(220,90%,56%)]"> /* hsl values */

Typography

Typography utilities control font size (text-xs, text-sm, text-base, text-lg, text-xl, text-2xl up to text-9xl), font weight (font-thin through font-black), line height (leading-tight, leading-normal, leading-relaxed), letter spacing (tracking-tight, tracking-wide), and text alignment (text-left, text-center, text-right, text-justify). The @tailwindcss/typography plugin adds a prose class that applies beautiful typographic defaults to raw HTML content like Markdown.

<!-- Font sizes -->
<p class="text-xs">   /* 12px */
<p class="text-sm">   /* 14px */
<p class="text-base"> /* 16px */
<p class="text-lg">   /* 18px */
<p class="text-xl">   /* 20px */
<p class="text-2xl">  /* 24px */
<h1 class="text-4xl font-bold"> /* 36px, font-weight: 700 */

<!-- Font weight -->
<p class="font-thin">       /* 100 */
<p class="font-normal">     /* 400 */
<p class="font-semibold">   /* 600 */
<p class="font-bold">       /* 700 */
<p class="font-extrabold">  /* 800 */

<!-- Line height, tracking, alignment -->
<p class="leading-relaxed tracking-wide text-center">

Responsive Prefixes

Tailwind uses a mobile-first responsive system. Every utility can be prefixed with a breakpoint modifier: sm: (min-width: 640px), md: (min-width: 768px), lg: (min-width: 1024px), xl: (min-width: 1280px), 2xl: (min-width: 1536px). Unprefixed classes apply at all screen sizes. This means you design for mobile first, then progressively enhance for larger screens. You can also use max-width variants like max-sm: to apply styles only below a breakpoint.

<!-- Mobile-first responsive design -->
<!-- No prefix = all screen sizes (mobile) -->
<!-- sm: = 640px+, md: = 768px+, lg: = 1024px+, xl: = 1280px+ -->

<div class="p-2 md:p-4 lg:p-6">
  8px on mobile, 16px on md, 24px on lg
</div>

<p class="text-sm md:text-base lg:text-lg">
  Font grows with screen size
</p>

<!-- Responsive grid: 1 col → 2 cols → 3 cols → 4 cols -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
  <div>Card 1</div>
  <div>Card 2</div>
  <div>Card 3</div>
</div>

<!-- Hide/show at breakpoints -->
<div class="hidden md:block">Only visible on md and above</div>
<div class="block md:hidden">Only visible below md</div>

3. Flexbox and Grid with Tailwind

Flexbox and CSS Grid are where Tailwind's utility-first approach truly shines. Common multi-property layout patterns collapse into a few readable utility classes.

Flexbox Utilities

Enable flexbox with flex. Control direction with flex-row (default) or flex-col. Use justify-start, justify-center, justify-end, justify-between, justify-around, justify-evenly for main-axis alignment, and items-start, items-center, items-end, items-stretch, items-baseline for cross-axis alignment. The gap utility replaces margins between flex items: gap-4 (gap: 16px), gap-x-8 (column gap), gap-y-2 (row gap). flex-wrap enables multi-line flex layouts. Use flex-1 for equal-width growing items, flex-none to prevent growth/shrink.

<!-- Centered flex container -->
<div class="flex justify-center items-center min-h-screen">
  Perfectly centered content
</div>

<!-- Navigation bar: logo left, links right -->
<nav class="flex justify-between items-center px-6 py-4">
  <div>Logo</div>
  <div class="flex gap-6">
    <a>Home</a>
    <a>About</a>
    <a>Contact</a>
  </div>
</nav>

<!-- Sidebar layout: sidebar + main content -->
<div class="flex gap-8">
  <aside class="w-64 flex-none">Sidebar</aside>
  <main class="flex-1">Main content grows to fill</main>
</div>

<!-- Vertical stack with equal spacing -->
<div class="flex flex-col gap-4">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
</div>

<!-- Wrapping tag list -->
<div class="flex flex-wrap gap-2">
  <span class="px-3 py-1 bg-blue-100 text-blue-700 rounded-full">React</span>
  <span class="px-3 py-1 bg-green-100 text-green-700 rounded-full">TypeScript</span>
  <span class="px-3 py-1 bg-purple-100 text-purple-700 rounded-full">Tailwind</span>
</div>

Grid Utilities

Enable CSS Grid with grid. Define column count with grid-cols-1 through grid-cols-12 or grid-cols-none. Items can span multiple columns with col-span-2, col-span-full. Use grid-rows-N for explicit row definitions. The gap utility works identically for grid. For responsive grids: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 creates a fully responsive multi-column layout without a single media query.

<!-- Responsive card grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
  <div class="bg-white rounded-lg shadow p-4">Card 1</div>
  <div class="bg-white rounded-lg shadow p-4">Card 2</div>
  <div class="bg-white rounded-lg shadow p-4">Card 3</div>
</div>

<!-- 12-column layout with spanning -->
<div class="grid grid-cols-12 gap-4">
  <aside class="col-span-3">Sidebar (3/12)</aside>
  <main class="col-span-9">Content (9/12)</main>
</div>

<!-- Full-width featured item + 3 cards -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
  <div class="col-span-2">Featured item (2 cols)</div>
  <div>Card 1</div>
  <div>Card 2</div>
</div>

<!-- Auto-fit columns (CSS subgrid-like) -->
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
  <!-- Items auto-fill available space -->
</div>

4. Dark Mode with Tailwind

Tailwind has first-class dark mode support via the dark: variant prefix. Prefix any utility with dark: to apply it only in dark mode. There are two configuration strategies: the class strategy (default in v3, activated when class="dark" is on the <html> element) and the media strategy (activated by the user's OS prefers-color-scheme preference). The class strategy is recommended for apps that let users toggle dark mode manually.

With class strategy, add dark to your <html> element (typically via JavaScript based on user preference or localStorage) and Tailwind will activate all dark: variants. Common patterns: bg-white dark:bg-gray-900 for backgrounds, text-gray-900 dark:text-gray-100 for text, border-gray-200 dark:border-gray-700 for borders. In Tailwind v4, the dark: variant works the same way but the strategy is configured in CSS rather than a config file.

/* tailwind.config.js — v3 dark mode strategy */
module.exports = {
  darkMode: "class",  // or "media"
  // ...
};

<!-- HTML: add "dark" class to html element -->
<html class="dark">

<!-- Component with dark mode variants -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100
            border border-gray-200 dark:border-gray-700
            rounded-lg p-6">
  <h2 class="text-xl font-bold text-gray-900 dark:text-white">Title</h2>
  <p class="text-gray-600 dark:text-gray-400">Body text</p>
  <button class="bg-blue-600 dark:bg-blue-500 hover:bg-blue-700
                 dark:hover:bg-blue-400 text-white px-4 py-2 rounded">
    Action
  </button>
</div>

// Toggle dark mode in JavaScript
function toggleDarkMode() {
  document.documentElement.classList.toggle("dark");
  localStorage.setItem("theme",
    document.documentElement.classList.contains("dark") ? "dark" : "light"
  );
}

Tailwind v4: In v4, dark mode strategy is configured via @variant dark (&:where(.dark, .dark *)) in your CSS, or use the built-in @media (prefers-color-scheme: dark) variant from @import "tailwindcss".

5. Custom Configuration (tailwind.config.js)

Tailwind is designed to be fully customized via tailwind.config.js (Tailwind v3) or CSS custom properties (Tailwind v4). You can extend the default theme, override colors, add custom fonts, define custom breakpoints, and register plugins.

In Tailwind v3, the configuration lives in tailwind.config.js or tailwind.config.ts. The theme.extend key lets you add to the default theme without removing defaults. Use theme (without extend) to replace defaults entirely. The content array tells Tailwind where to scan for class names — it must cover all files that use Tailwind classes.

// tailwind.config.js — Tailwind v3
/** @type {import("tailwindcss").Config} */
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  darkMode: "class",
  theme: {
    extend: {
      colors: {
        brand: {
          50:  "#eff6ff",
          500: "#3b82f6",
          900: "#1e3a8a",
        },
        primary: "#6d28d9",
      },
      fontFamily: {
        sans: ["Inter", "system-ui", "sans-serif"],
        mono: ["Fira Code", "monospace"],
      },
      screens: {
        "3xl": "1920px",  // custom breakpoint
      },
      borderRadius: {
        "4xl": "2rem",
      },
      spacing: {
        "128": "32rem",  // w-128 = 512px
      },
      animation: {
        "fade-in": "fadeIn 0.3s ease-out",
      },
      keyframes: {
        fadeIn: {
          "0%": { opacity: "0", transform: "translateY(-8px)" },
          "100%": { opacity: "1", transform: "translateY(0)" },
        },
      },
    },
  },
  plugins: [
    require("@tailwindcss/typography"),
    require("@tailwindcss/forms"),
    require("@tailwindcss/container-queries"),
  ],
};
/* globals.css — Tailwind v4 CSS-first configuration */
@import "tailwindcss";

@theme {
  /* Custom colors — exposed as var(--color-brand-500) */
  --color-brand-50:  #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a8a;
  --color-primary:   #6d28d9;

  /* Custom fonts */
  --font-sans: "Inter", system-ui, sans-serif;
  --font-mono: "Fira Code", monospace;

  /* Custom breakpoints */
  --breakpoint-3xl: 1920px;

  /* Custom spacing */
  --spacing-128: 32rem;
}

/* No tailwind.config.js needed! */
/* Custom variants */
@variant dark (&:where(.dark, .dark *));

/* Component styles using @apply */
@layer components {
  .btn-primary {
    @apply bg-brand-500 text-white px-4 py-2 rounded-lg
           hover:bg-brand-600 transition-colors;
  }
}

Official Plugins

Tailwind provides official plugins for common patterns: @tailwindcss/typography (prose class for rich text), @tailwindcss/forms (opinionated form resets), @tailwindcss/aspect-ratio (aspect-ratio utilities before native CSS support), and @tailwindcss/container-queries (container query variants). Third-party plugins like tailwind-animate and daisyUI extend Tailwind further with animations and pre-built component classes.

6. Components and Reusability: @apply, cva, and Design Patterns

One of the common criticisms of utility-first CSS is that long class strings become hard to read and maintain. Tailwind provides several approaches to component extraction and reusability.

The @apply directive lets you compose utility classes into a custom CSS class. This is useful for third-party HTML you cannot control, or for extracting truly reusable component styles. However, for React/Vue/Svelte component-based architectures, the recommended approach is to extract a component (a JSX/Vue file) rather than using @apply, since component files provide better encapsulation, props-based variants, and IDE support.

The cva (Class Variance Authority) library provides a TypeScript-first API for defining component variants using Tailwind classes. Combined with the cn() utility (clsx + tailwind-merge), it enables fully typed, composable component APIs similar to Stitches or Vanilla Extract but using Tailwind as the styling layer. This is the approach used by shadcn/ui — the most popular React UI component library built on Tailwind.

/* @apply: composing utilities into a CSS class */
@layer components {
  .prose-content h1 {
    @apply text-3xl font-bold text-gray-900 mb-4 mt-8;
  }
  .prose-content p {
    @apply text-gray-700 leading-relaxed mb-4;
  }
}
// Button component with cva (Class Variance Authority)
import { cva, type VariantProps } from "cva";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  // Base classes
  "inline-flex items-center justify-center rounded-md font-medium
   transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2
   disabled:opacity-50 disabled:pointer-events-none",
  {
    variants: {
      variant: {
        default:     "bg-blue-600 text-white hover:bg-blue-700",
        destructive: "bg-red-600 text-white hover:bg-red-700",
        outline:     "border border-gray-300 bg-white hover:bg-gray-50",
        ghost:       "hover:bg-gray-100 hover:text-gray-900",
        link:        "underline-offset-4 hover:underline text-blue-600",
      },
      size: {
        sm:      "h-8  px-3 text-sm",
        default: "h-10 px-4 py-2",
        lg:      "h-12 px-8 text-lg",
        icon:    "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

export function Button({ className, variant, size, ...props }: ButtonProps) {
  return (
    <button
      className={cn(buttonVariants({ variant, size }), className)}
      {...props}
    />
  );
}

// Usage — fully typed
<Button variant="outline" size="lg">Click me</Button>
<Button variant="destructive">Delete</Button>

7. Tailwind CSS v4: CSS-First Configuration and New Features

Tailwind CSS v4 (released in early 2025) is the biggest evolution of the framework since its initial release. The headline feature is CSS-first configuration: instead of tailwind.config.js, you configure Tailwind directly in your CSS file using the new @import "tailwindcss" directive and @theme block.

Key Changes in Tailwind v4

Tailwind v4 introduces: CSS-first configuration (no more tailwind.config.js), a new Rust-based engine (Oxide) that is up to 5x faster for full builds and 100x faster for incremental builds, native CSS cascade layers, CSS custom properties for all design tokens (colors, spacing, fonts are exposed as var(--color-blue-500) etc.), new 3D transform utilities (rotate-x, rotate-y, perspective), inset-shadow and text-shadow utilities, expanded container query support with @min and @max variants, and a new @starting-style variant for entry animations. The content configuration is now automatic — Tailwind uses CSS to detect template files without explicit content paths.

/* Tailwind v4 — New 3D transform utilities */
<div class="rotate-x-45 rotate-y-12 perspective-500">
  3D rotated card
</div>

/* Text shadow and inset shadow (new in v4) */
<h1 class="text-shadow-lg text-shadow-black/20">
  Heading with text shadow
</h1>
<div class="inset-shadow-sm inset-shadow-black/10 rounded-lg">
  Inset shadow container
</div>

/* Container queries — @min and @max variants */
<div class="@container">
  <div class="@min-sm:grid-cols-2 @min-lg:grid-cols-3 grid gap-4">
    Responds to container width, not viewport
  </div>
</div>

/* @starting-style for entry animations */
.toast {
  opacity: 1;
  transform: translateY(0);
  transition: all 0.3s;

  @starting-style {
    opacity: 0;
    transform: translateY(1rem);
  }
}

Migrating from v3 to v4

Tailwind provides an automated migration tool: npx @tailwindcss/upgrade. Breaking changes include: tailwind.config.js is replaced by CSS @theme configuration, the @tailwind base/components/utilities directives are replaced by @import "tailwindcss", some utilities have been renamed (shadow-sm is now shadow-xs, ring becomes ring-1 by default), and the PostCSS plugin has been split into a separate @tailwindcss/postcss package. For most projects, the automated migration tool handles the majority of changes.

# Run the automated migration tool
npx @tailwindcss/upgrade

# Install Tailwind v4 + PostCSS plugin
npm install tailwindcss@next @tailwindcss/postcss@next

/* Before (v3 globals.css) */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* After (v4 globals.css) */
@import "tailwindcss";

/* Before (postcss.config.js v3) */
module.exports = {
  plugins: { tailwindcss: {}, autoprefixer: {} },
};

/* After (postcss.config.js v4) */
module.exports = {
  plugins: { "@tailwindcss/postcss": {} },
};

// Key class renames in v4
// shadow-sm   → shadow-xs
// shadow       → shadow-sm
// ring         → ring-1 (ring defaults to 1px now)
// overflow-ellipsis → text-ellipsis
// decoration-slice  → box-decoration-slice

8. Integration with React and Next.js

Tailwind CSS integrates seamlessly with React and Next.js. In a Next.js 14+ project, Tailwind is installed via npx create-next-app which includes Tailwind setup by default. The integration requires adding Tailwind to PostCSS config and importing the Tailwind CSS file in your root layout.

# Create Next.js 14+ project with Tailwind (auto-configured)
npx create-next-app@latest my-app --typescript --tailwind

# Install cn utility dependencies
npm install clsx tailwind-merge

// lib/utils.ts — the cn() utility
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// Usage in a React component
import { cn } from "@/lib/utils";

interface CardProps {
  className?: string;
  highlighted?: boolean;
}

export function Card({ className, highlighted }: CardProps) {
  return (
    <div
      className={cn(
        "rounded-lg border bg-white p-6 shadow-sm",
        highlighted && "border-blue-500 ring-2 ring-blue-500/20",
        className  // consumer can override
      )}
    />
  );
}

The cn() Utility

The cn() utility function is a thin wrapper around clsx (conditional class joining) and tailwind-merge (intelligent Tailwind class conflict resolution). It is essential for building component libraries with Tailwind because it safely merges classes: cn("px-4 py-2", condition && "bg-blue-500", className) will apply the conditional class only when true, and tailwind-merge ensures that conflicting classes like p-4 and px-2 are resolved correctly (px-2 wins if it comes last, similar to CSS specificity).

shadcn/ui and Headless UI

shadcn/ui (shadcn.com/ui) is a collection of copy-paste React components built with Tailwind CSS and Radix UI primitives. Unlike traditional UI libraries, shadcn/ui components are added directly to your project — you own the code and can customize everything. Each component is typed with TypeScript and uses cva for variant management. Headless UI (headlessui.com) by the Tailwind Labs team provides unstyled, fully accessible UI components (dialog, menu, listbox, etc.) designed to be styled with Tailwind. For 2025 projects, the recommended stack is Next.js + Tailwind CSS + shadcn/ui + cva for a fully typed, accessible component system.

# Initialize shadcn/ui in a Next.js + Tailwind project
npx shadcn@latest init

# Add components — code is copied to your project
npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add dialog
npx shadcn@latest add dropdown-menu

// Usage — fully typed, Tailwind-styled
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardContent } from "@/components/ui/card";

export default function Page() {
  return (
    <Card className="max-w-md">
      <CardHeader>
        <h2 className="text-xl font-semibold">Plan</h2>
      </CardHeader>
      <CardContent>
        <p>Includes all features</p>
        <Button variant="default" size="lg" className="w-full mt-4">
          Get Started
        </Button>
      </CardContent>
    </Card>
  );
}

9. Tailwind vs Bootstrap vs CSS Modules vs Styled Components

Choosing the right CSS approach depends on team size, project scale, design system requirements, and developer preference. Here is a detailed comparison of the four most popular approaches.

DimensionTailwind CSSBootstrapCSS ModulesStyled Components
ParadigmUtility-first, inline in HTMLComponent-first, pre-builtScoped CSS, CSS-likeCSS-in-JS, tagged templates
Bundle size (gzipped)5–15 KB~22 KB min CSSDepends on usage~13KB runtime
TypeScript supportVia cva/tailwind-mergeVia @typesNative, typed modulesBuilt-in, fully typed
Runtime performanceZero runtime, pure static CSSZero runtime (CSS only)Zero runtime (build time)Runtime overhead
Learning curveMedium (learn utilities)Low (familiar class names)Low (just CSS)Medium (CSS in JS)
Design systemBuilt-in, highly customizableBuilt-in, hard to overrideManual — build your ownManual — build your own
Responsive designBreakpoint prefixes (sm:/md:/lg:)Grid classes (col-md-6)Standard media queriesStandard media queries
Dark modedark: prefix, built-inv5.3+ built-in supportMedia queries (manual)ThemeProvider (runtime)
Best forRapid iteration, design systemsPrototypes, admin toolsLegacy projects, fine controlDynamic theming, libraries

10. Frequently Asked Questions

Does Tailwind CSS produce a large CSS bundle?

No — the JIT compiler generates only the CSS for classes you actually use. A typical Tailwind production bundle is 5–15KB gzipped, which is significantly smaller than Bootstrap (about 22KB) or other component frameworks. The unused class purging works by scanning your content files for class name strings and only generating CSS for found classes. With Tailwind v4, the engine is even more efficient and the content detection is automatic.

How do I handle class conflicts in Tailwind (e.g., p-4 and px-2 on the same element)?

In raw HTML, the last class in the stylesheet wins (not the last class in the HTML attribute). In React, use the tailwind-merge library (or the cn() utility from clsx + tailwind-merge). tailwind-merge intelligently resolves conflicts: cn("p-4 px-2") results in p-4 py-4 (px-2 overrides the horizontal part of p-4). This is especially important in component libraries where parent code passes className props that may conflict with internal defaults.

Does Tailwind CSS work with PurgeCSS?

Tailwind v3+ has built-in unused CSS removal through its JIT engine — you do not need to configure PurgeCSS separately. The content configuration in tailwind.config.js (v3) or the automatic detection in v4 tells Tailwind which files to scan. Tailwind's built-in purging is faster and more accurate than PurgeCSS for Tailwind classes because it understands the Tailwind class name format natively. For non-Tailwind CSS in the same project, you can still use PurgeCSS in your build pipeline.

Can I use Tailwind CSS without a build step?

Yes — Tailwind provides a standalone CLI executable and a CDN play version for prototyping. The CDN version (cdn.tailwindcss.com) generates all possible utilities on demand in the browser using the new v4 engine, which makes it suitable for demos and quick prototypes. However, for production, a build step is always recommended to generate a minimal, optimized CSS file. The Vite + Tailwind integration is the fastest build setup for new projects.

How do I customize Tailwind's color palette?

In Tailwind v3, extend colors in tailwind.config.js under theme.extend.colors. You can add new color names, override existing ones, or add additional shades. In Tailwind v4, define custom colors as CSS custom properties in your @theme block: --color-brand: #6d28d9 makes brand-500 available. The new v4 approach uses native CSS variables, so your custom colors are also accessible in JavaScript via getComputedStyle.

How does Tailwind CSS affect performance compared to hand-written CSS?

For runtime performance, Tailwind generates static CSS with no JavaScript overhead — it is as fast as any static CSS file. The utility classes avoid specificity issues that require !important overrides, making cascade management predictable. For large applications, Tailwind's small and consistent CSS bundle actually improves initial page load compared to growing bespoke stylesheets. The consistency of the design system also reduces the chance of one-off style experiments that grow CSS over time.

What is the difference between @apply and extracting a component?

@apply lets you compose Tailwind utilities into a CSS class. It is appropriate for styling third-party HTML content you cannot modify (like Markdown output or CMS content). For components you control (React/Vue/Svelte), it is almost always better to extract a component file — you get props-based variants, TypeScript types, co-located logic, and better IDE support. Overuse of @apply defeats the purpose of utility-first CSS and can lead to CSS that is hard to maintain.

Should I use Tailwind CSS v3 or v4 for a new project in 2025?

For new projects started in 2025, use Tailwind v4. The new CSS-first configuration model is simpler, the Oxide engine is dramatically faster, and v4 aligns better with modern CSS standards. If you are on an existing v3 project, the migration tool handles most breaking changes automatically. The ecosystem (shadcn/ui, Headless UI, plugins) has largely updated to support v4. The only reason to stay on v3 would be a strict dependency on a plugin that has not yet been updated for v4.

Tailwind CSS has earned its place as the most popular utility-first CSS framework for a reason: it is fast to write, predictable to debug, trivially tree-shakable, and deeply customizable. Whether you are building a simple landing page or a complex SaaS dashboard, Tailwind's constraints enforce consistency while its escape hatches (arbitrary values, CSS variables) handle any edge case. With Tailwind v4's CSS-first configuration and the Oxide engine, the developer experience has reached a new level of speed and simplicity. The combination of Next.js + Tailwind CSS + shadcn/ui + cva represents the most productive React styling stack available today.

𝕏 Twitterin LinkedIn
도움이 되었나요?

최신 소식 받기

주간 개발 팁과 새 도구 알림을 받으세요.

스팸 없음. 언제든 구독 해지 가능.

Try These Related Tools

{ }CSS Minifier / Beautifier🎨Color Converter

Related Articles

React Hooks 완벽 가이드: useState, useEffect, 커스텀 Hooks

실용적인 예제로 React Hooks 마스터. useState, useEffect, useContext, useReducer, useMemo, useCallback, 커스텀 Hooks, React 18+ 동시성 Hooks 학습.

웹 성능 최적화 가이드: Core Web Vitals, 캐싱, React/Next.js

웹 성능 최적화를 마스터하세요. Core Web Vitals(LCP, FID, CLS), 이미지 최적화, 코드 분할, 캐싱 전략, React/Next.js 성능, Lighthouse 점수의 완전 가이드.

Next.js 심층 가이드 2026: App Router, Server Components, 프로덕션 패턴

Next.js 종합 가이드. App Router, React Server Components, 데이터 페칭, Server Actions, 이미지 최적화, 미들웨어, 배포 전략 완전 정복.