Tailwind CSS v4 심층 분석 — Oxide 엔진, CSS-first 설정, 마이그레이션 전략
Tailwind CSS v4의 Rust 기반 Oxide 엔진, @theme 디렉티브를 통한 CSS-first 설정, Cascade Layers 활용, 컨테이너 쿼리 네이티브 지원까지. v3에서 v4로의 마이그레이션 실전 가이드를 코드 수준에서 분석한다.
Tailwind CSS v4가 2025년 1월 정식 릴리스되었고, 2026년 3월 현재 v4.1까지 안정화되면서 프로덕션 마이그레이션이 본격화되고 있다. 가장 큰 변화는 설정 파일이 사라졌다는 것이다. tailwind.config.js 대신 CSS 파일 안에서 @theme 디렉티브로 디자인 토큰을 정의하고, PostCSS 없이 독립 실행되는 Rust 기반 엔진(Oxide)이 빌드를 처리한다.
이 글은 v3에서 v4로 넘어가면서 실제로 무엇이 달라지는지, 마이그레이션 시 어떤 부분에서 막히는지, 그리고 새 기능이 실무 CSS 설계에 어떤 의미를 갖는지를 코드 수준에서 분석한다.
기준일: 2026년 3월 | Tailwind CSS v4.1, Vite 6+, Next.js 15+ 환경 기준
Oxide 엔진 — Rust로 다시 쓴 Tailwind의 심장부
v4의 핵심은 Oxide 엔진이다. Tailwind의 코어 로직 — 클래스 감지(content scanning), 유틸리티 생성, CSS 출력 — 을 JavaScript에서 Rust로 재작성했다. 이 변경이 가져온 실질적 차이는 세 가지다.
항목
v3 (JS)
v4 (Oxide/Rust)
초기 빌드
~300ms (중간 규모)
~35ms (약 10배 빠름)
증분 빌드
~80ms
~5ms 이하
PostCSS 의존성
필수
선택 (독립 CLI/Vite 플러그인)
content 설정
tailwind.config.js에 glob 패턴 명시
자동 감지 (설정 불필요)
Oxide가 content 경로를 자동으로 감지하는 방식은 프로젝트 루트에서 .gitignore를 참조해 스캔 대상을 결정하는 것이다. node_modules, .git, 바이너리 파일 등은 자동으로 제외된다. 대부분의 프로젝트에서는 별도 설정 없이 정확하게 동작하지만, monorepo에서 패키지 경계를 넘어야 하는 경우에는 @source 디렉티브로 명시적 경로를 추가해야 한다.
v3 대비 v4의 빌드 성능 비교 — 초기 빌드 약 10배, 증분 빌드 약 15배 개선 (출처: tailwindcss.com/blog/tailwindcss-v4)
PostCSS 의존성이 사라진 점은 빌드 파이프라인 단순화에 큰 영향을 준다. v3에서는 postcss.config.js에 tailwindcss와 autoprefixer를 플러그인으로 등록해야 했다. v4에서는 Vite를 사용하는 프로젝트라면 @tailwindcss/vite 플러그인 하나만 추가하면 된다. Webpack 환경이나 Vite를 쓰지 않는 경우에는 여전히 PostCSS 플러그인(@tailwindcss/postcss)을 사용할 수 있지만, 권장 경로는 Vite 플러그인이다.
v4에서 Vite 프로젝트 설정 (PostCSS 불필요)
// vite.config.ts
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
tailwindcss(),
],
});
// CSS 파일 (app.css) — @tailwind 디렉티브 대신 @import 사용
@import "tailwindcss";
CSS-first 설정 — tailwind.config.js가 사라진 이유
v3에서 디자인 토큰(색상, 간격, 폰트 등)을 커스터마이징하려면 tailwind.config.js의 theme.extend 객체를 수정해야 했다. v4에서는 이 작업을 CSS 안에서 직접 한다. @theme 디렉티브가 기존 설정 파일의 역할을 대체한다.
이 변화의 설계 의도는 명확하다. CSS 커스텀 프로퍼티(CSS Variables)는 이미 브라우저가 이해하는 표준이고, 빌드 타임에 JavaScript로 변환할 필요가 없다. v4는 모든 디자인 토큰을 CSS 변수로 노출하므로, var(--color-blue-500)처럼 Tailwind 외부에서도 직접 참조할 수 있다.
@theme 안에서 정의한 변수명은 Tailwind 유틸리티와 자동으로 연결된다. --color-brand-500을 정의하면 bg-brand-500, text-brand-500, border-brand-500 등의 유틸리티 클래스가 자동 생성된다. 네이밍 컨벤션이 곧 API인 셈이다.
@theme에서 지원하는 네임스페이스는 아래와 같다.
--color-* → 색상 유틸리티 (bg, text, border, ring 등)
--font-* → font-{name} 유틸리티
--spacing-* → 간격 유틸리티 (p, m, gap, w, h 등)
--radius-* → rounded-{name} 유틸리티
--shadow-* → shadow-{name} 유틸리티
--breakpoint-* → 반응형 프리픽스 (sm, md, lg 등)
--animate-* → animate-{name} 유틸리티 + @keyframes
기존 tailwind.config.js가 있는 프로젝트는 @config 디렉티브로 호환성을 유지할 수 있다. 점진적 마이그레이션이 가능하다는 의미다.
기존 config 파일을 유지하면서 v4 사용
/* app.css — 점진적 마이그레이션 */
@import "tailwindcss";
@config "../tailwind.config.js"; /* 기존 설정 파일 경로 */
/* @theme으로 신규 토큰만 추가 */
@theme {
--color-accent: oklch(0.7 0.25 330);
}
Native Cascade Layers — CSS 우선순위 문제의 종결
v4는 CSS의 @layer 규칙을 네이티브로 활용한다. Tailwind가 생성하는 CSS는 세 개의 레이어로 분리된다.
@layer theme — 디자인 토큰 (CSS 변수 정의)
@layer base — Preflight (리셋) + 기본 스타일
@layer utilities — 유틸리티 클래스
이 구조가 실무에서 해결하는 문제는 specificity(특이도) 충돌이다. v3에서는 커스텀 CSS가 Tailwind 유틸리티보다 나중에 선언되면 유틸리티를 덮어쓰는 문제가 빈번했다. v4에서는 @layer utilities가 사용자 정의 CSS보다 항상 높은 우선순위를 가지므로, 유틸리티 클래스가 확실하게 적용된다.
서드파티 CSS 라이브러리(예: 달력 위젯, 리치 텍스트 에디터)와의 충돌도 줄어든다. 외부 CSS를 @layer 없이 작성하면 Tailwind 유틸리티보다 낮은 우선순위를 갖게 되므로, !important 없이도 유틸리티로 오버라이드할 수 있다.
실무에서 바로 쓸 수 있는 v4 신규 기능
엔진 교체와 설정 방식 변경 외에도, v4는 실질적인 CSS 기능 확장이 많다. 아래는 프로덕션에서 즉시 활용할 수 있는 주요 추가 사항이다.
1. 컨테이너 쿼리 네이티브 지원
v3에서는 @tailwindcss/container-queries 플러그인이 필요했지만, v4는 코어에 내장했다. @container 프리픽스로 부모 컨테이너의 크기에 반응하는 레이아웃을 만들 수 있다.
컨테이너 쿼리 — 부모 크기 기반 반응형
<!-- 부모에 @container 지정 -->
<div class="@container">
<!-- 컨테이너 너비 기준 반응형 -->
<div class="grid grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3 gap-4">
<div class="p-4 @sm:p-6">카드 내용</div>
</div>
</div>
<!-- 이름 있는 컨테이너 -->
<div class="@container/sidebar">
<nav class="@md/sidebar:flex @md/sidebar:flex-col">
<!-- sidebar 컨테이너가 md 이상일 때만 세로 배치 -->
</nav>
</div>
2. P3 와이드 가뮤 색상
v4의 기본 팔레트는 oklch() 색상 공간을 사용한다. P3 디스플레이(MacBook, iPhone 등)에서 sRGB보다 더 선명한 색상을 표현할 수 있다. 기존 hex 값도 여전히 사용 가능하지만, @theme에서 oklch로 정의하면 색상 보간(그라데이션, 투명도 변환)이 더 자연스럽다.
3. 3D 트랜스폼 유틸리티
rotate-x-*, rotate-y-*, perspective-* 등 3D 변환 유틸리티가 추가됐다. 카드 플립, 3D 캐러셀 등을 유틸리티만으로 구현할 수 있다.
4. @starting-style로 진입 애니메이션
CSS @starting-style 규칙과 연동되는 starting: 변형자가 추가됐다. 요소가 DOM에 삽입될 때의 초기 스타일을 정의할 수 있어, JavaScript 없이 마운트 애니메이션을 구현할 수 있다.
@starting-style을 활용한 페이드인 애니메이션
<!-- JavaScript 없이 마운트 시 페이드인 -->
<dialog
class="opacity-100 translate-y-0 transition-all duration-300
starting:opacity-0 starting:translate-y-4
backdrop:bg-black/50 backdrop:backdrop-blur-sm"
popover
>
<div class="p-6 rounded-xl bg-white shadow-2xl">
팝오버 내용
</div>
</dialog>
@theme 디렉티브로 CSS 안에서 직접 디자인 토큰을 정의하는 v4의 새로운 설정 방식 (출처: tailwindcss.com/docs/theme)
v3에서 v4 마이그레이션 — 실전 체크리스트
Tailwind 팀이 제공하는 공식 마이그레이션 도구 @tailwindcss/upgrade가 대부분의 자동 변환을 처리하지만, 수동 개입이 필요한 케이스가 있다. 프로덕션 마이그레이션에서 실제로 발생하는 이슈를 정리한다.
공식 업그레이드 도구 실행
# 자동 마이그레이션 실행
npx @tailwindcss/upgrade
# 실행 후 변경 사항:
# 1. tailwind.config.js → CSS @theme 블록으로 변환
# 2. @tailwind base/components/utilities → @import "tailwindcss"
# 3. 폐기된 유틸리티 클래스 자동 교체
# 4. postcss.config.js에서 tailwindcss 플러그인 제거
자동 변환이 처리하지 못하는 주요 케이스는 아래와 같다.
이슈
원인
해결
커스텀 플러그인 미동작
v4 플러그인 API 변경
@utility, @variant 디렉티브로 재작성
@apply 내 임의 값 실패
v4에서 @apply 범위 제한
인라인 유틸리티 또는 CSS 변수로 전환
다크 모드 class 전략 미적용
v4는 prefers-color-scheme이 기본
@variant dark (&.dark *)로 클래스 기반 전환
monorepo에서 클래스 누락
자동 감지가 패키지 경계 못 넘음
@source "../packages/ui/src" 추가
theme() 함수 폐기
CSS 변수로 대체됨
theme(colors.blue.500) → var(--color-blue-500)
⚠️ 다크 모드 주의: v3의 darkMode: "class" 설정을 사용하던 프로젝트는 v4에서 반드시 @variant dark (&.dark *)를 CSS에 추가해야 한다. 기본값이 prefers-color-scheme: dark로 변경되었기 때문에, 별도 설정 없이 마이그레이션하면 수동 토글이 작동하지 않는다.
마이그레이션 후 반드시 확인해야 할 체크리스트를 정리한다.
빌드 출력 크기 비교 — v4는 미사용 유틸리티를 더 정확하게 트리쉐이킹하므로, 대부분 CSS 번들이 줄어든다
브라우저 지원 범위 확인 — @layer, oklch(), @container 등은 2023년 이후 브라우저에서 지원. IE11은 v3부터 이미 미지원
CI/CD 파이프라인에서 빌드 테스트 — PostCSS 설정이 제거되면서 기존 빌드 스크립트가 실패할 수 있음
Storybook/Chromatic 스냅샷 비교 — 색상 공간 변경(sRGB→oklch)으로 미세한 색상 차이가 발생할 수 있음
v4 시대의 CSS 설계 패턴
v4의 변경은 단순한 버전 업그레이드가 아니라, Tailwind로 CSS를 설계하는 방식 자체를 바꾼다. 몇 가지 패턴 변화를 짚어본다.
토큰 중심 설계
v3에서는 디자인 토큰이 JavaScript 객체 안에 숨어 있었다. 디자이너가 토큰을 확인하려면 tailwind.config.js를 열거나, Tailwind CSS IntelliSense 익스텐션에 의존해야 했다. v4에서는 CSS 파일을 열면 모든 토큰이 보인다. Figma 토큰 → CSS 변수 → Tailwind 유틸리티로 이어지는 파이프라인이 자연스러워졌다.
@utility로 커스텀 유틸리티 만들기
v3의 플러그인 API(addUtilities, matchUtilities)를 CSS 안에서 선언적으로 대체한다. JavaScript 플러그인 파일을 만들 필요 없이, CSS에서 바로 유틸리티를 정의하고 사용할 수 있다.
v3에서 addVariant 플러그인으로 구현하던 커스텀 변형자도 CSS로 이동했다. 예를 들어, 특정 data 속성이 있을 때만 스타일을 적용하는 변형자를 정의할 수 있다.
@variant 디렉티브로 커스텀 변형자 정의
/* data-state="open"인 요소에 적용되는 변형자 */
@variant open (&[data-state="open"]);
/* 사용: <div class="open:opacity-100 open:translate-y-0"> */
/* 스크린 리더 전용 변형자 */
@variant sr-only (&:not(:focus):not(:active));
/* 프린트 전용 */
@variant print (@media print);
v3 → v4 마이그레이션 후 프로젝트 구조 변화 — config 파일이 CSS로 흡수되면서 빌드 파이프라인이 단순해진다
지금 마이그레이션해야 하는가
v4.1이 나온 2026년 3월 시점에서, 마이그레이션 판단은 프로젝트 상황에 따라 나뉜다.
상황
판단
이유
신규 프로젝트
v4로 시작
v3를 선택할 이유가 없다
Vite 기반 프로젝트
적극 마이그레이션
빌드 속도 개선 체감이 크고, 마이그레이션 난이도가 낮다
Webpack 기반 레거시
검토 후 결정
PostCSS 플러그인은 지원하지만, Vite 대비 이점이 제한적
커스텀 플러그인 다수
단계적 전환
플러그인 재작성 비용이 크므로 @config 호환 모드로 시작
IE11/레거시 브라우저 지원 필요
v3 유지
@layer, oklch 등 최신 CSS 기능 의존
v4로의 전환은 Tailwind 역사상 가장 큰 아키텍처 변경이다. 하지만 그 방향은 일관적이다 — CSS가 할 수 있는 일을 JavaScript에서 빼앗지 않겠다는 것. PostCSS 의존성 제거, CSS-first 설정, 네이티브 @layer 활용 모두 같은 철학에서 나왔다.
결국 Tailwind v4는 "유틸리티 CSS 프레임워크"에서 "CSS 위에 얹는 얇은 도구 레이어"로 포지셔닝을 재정의한 릴리스다. CSS 자체가 진화하면서 프레임워크가 대신해주던 기능(cascade layers, container queries, nesting)을 브라우저가 직접 처리하게 되었고, Tailwind는 그 위에서 DX(개발자 경험)만 담당하겠다는 선택을 한 것이다.