WebPiki
tutorial

Tailwind CSS v4 마이그레이션 가이드 — v3에서 뭐가 달라졌나

Tailwind CSS v4의 주요 변경점과 v3에서의 마이그레이션 방법. CSS 기반 설정, 새 유틸리티, 성능 개선까지 정리.

새로운 색으로 칠해지는 스타일 변화

Tailwind CSS v4는 v3와 겉보기에는 비슷해 보이지만, 내부 구조가 상당히 달라졌다. 가장 눈에 띄는 변화는 tailwind.config.js가 사라지고 CSS 파일에서 직접 설정하는 방식으로 바뀐 거다. 처음엔 낯설 수 있는데, 적응하면 오히려 더 직관적이다.

가장 큰 변화 — 설정이 CSS로 갔다

v3에서는 tailwind.config.js에 색상, 폰트, 브레이크포인트 등을 JavaScript 객체로 정의했다. v4에서는 이걸 CSS 파일 안에서 @theme 지시어로 정의한다.

/* v3: tailwind.config.js */
/* module.exports = {
  theme: {
    extend: {
      colors: {
        brand: '#3b82f6',
      },
      fontFamily: {
        sans: ['Pretendard', 'sans-serif'],
      },
    },
  },
} */

/* v4: app.css */
@import "tailwindcss";

@theme {
  --color-brand: #3b82f6;
  --font-sans: 'Pretendard', sans-serif;
}

CSS 커스텀 프로퍼티(CSS 변수)를 직접 정의하는 방식이다. JavaScript 설정 파일이 없어지니까 빌드 체인이 단순해지고, CSS만 보면 전체 디자인 토큰을 파악할 수 있다. 에디터에서 CSS 파일 하나만 열면 프로젝트의 색상, 폰트, 간격 체계가 한눈에 들어온다는 게 체감되는 차이다.

또 하나의 장점은 CSS 변수이기 때문에 런타임에서도 접근할 수 있다는 것이다. JavaScript에서 getComputedStyle로 읽거나, 다크 모드 전환 시 변수를 오버라이드하는 패턴이 더 자연스러워진다.

content 설정이 필요 없다

v3에서 가장 자주 실수하던 부분이 content 배열 설정이었다. 어떤 파일에서 Tailwind 클래스를 쓰는지 지정해야 했고, 이걸 빠뜨리면 스타일이 적용되지 않았다.

// v3: 이거 빠뜨려서 한 번쯤 삽질해봤을 거다
module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
}

새 디렉토리를 추가하거나 파일 확장자가 바뀔 때마다 이 배열을 업데이트해야 했다. v4에서는 자동 콘텐츠 감지가 기본이다. 프로젝트 디렉토리를 알아서 스캔해서 사용 중인 클래스를 찾아낸다. content 설정이 필요 없어졌다. 이것만으로도 마이그레이션할 가치가 있다고 느끼는 사람도 있을 거다.

특정 경로를 제외하거나 추가하고 싶으면 @source 지시어를 쓴다:

@import "tailwindcss";
@source "../node_modules/some-ui-lib";

새 엔진 — Oxide

v4는 Rust로 작성된 새 엔진(Oxide)을 사용한다. 체감되는 차이점:

빌드 속도 — v3 대비 상당히 빨라졌다. 대규모 프로젝트에서 특히 차이가 크다. PostCSS 플러그인 방식이 아니라 독립적인 CSS 처리 엔진으로 동작하기 때문이다. 개발 서버에서 핫 리로드 속도도 빨라진다.

설치 간소화postcssautoprefixer를 별도로 설치할 필요가 없다. @tailwindcss/postcss@tailwindcss/vite 플러그인 하나만 설치하면 된다. 의존성이 줄어드니까 node_modules 크기도 줄고 설치도 빨라진다.

# v3
npm install tailwindcss postcss autoprefixer

# v4
npm install tailwindcss @tailwindcss/vite  # Vite 프로젝트

달라진 유틸리티들

색상 불투명도

<!-- v3 -->
<div class="bg-blue-500/50">

<!-- v4 — 동일. 근데 CSS 변수로도 제어 가능 -->
<div class="bg-blue-500/50">
<div class="bg-[oklch(0.5_0.2_250/50%)]">

v4는 내부적으로 oklch 색상 공간을 사용한다. 사용자가 직접 oklch 값을 쓸 수도 있는데, oklch는 인간의 색 인지와 더 가까워서 색상 팔레트를 만들 때 일관된 밝기를 유지하기 쉽다. 같은 L 값을 가진 색상은 시각적으로도 비슷한 밝기로 보인다는 뜻이다. sRGB에서는 이게 보장되지 않는다.

새로운 유틸리티들

<!-- 컨테이너 쿼리 -->
<div class="@container">
  <div class="@lg:flex @md:grid">...</div>
</div>

<!-- 3D 변환 -->
<div class="rotate-x-45 perspective-800">

<!-- 그라디언트 개선 -->
<div class="bg-linear-to-r from-blue-500 to-purple-500">
<!-- bg-gradient-to-r → bg-linear-to-r 로 변경 -->

컨테이너 쿼리가 네이티브 지원된다. v3에서는 @tailwindcss/container-queries 플러그인이 필요했는데, v4에서는 내장이다. 컴포넌트가 뷰포트가 아닌 자기 부모의 크기에 따라 레이아웃을 바꿀 수 있어서, 재사용 가능한 컴포넌트를 만들 때 유용하다. 사이드바에 넣을 때와 메인 콘텐츠에 넣을 때 같은 컴포넌트가 자동으로 다르게 렌더링되는 식이다.

3D 변환 유틸리티도 새로 추가됐다. rotate-x-45, rotate-y-30, perspective-800 같은 클래스로 3D 효과를 줄 수 있다. 이전에는 커스텀 CSS가 필요했던 부분이다.

이름이 바뀐 것들

일부 유틸리티의 이름이 더 일관성 있게 바뀌었다:

v3v4
bg-gradient-to-rbg-linear-to-r
bg-opacity-50bg-black/50 (이미 v3에서도 가능)
shadow-smshadow-xs (새 단계 추가)
ring-offset-2ring-offset-2 (동일)

대부분은 v3 문법이 그대로 작동하고, 변경된 것도 자동 마이그레이션 도구로 처리할 수 있다.

커스텀 유틸리티 만들기

v4에서는 @utility 지시어로 커스텀 유틸리티를 CSS 안에서 직접 정의할 수 있다. v3에서 플러그인으로 해야 했던 작업이 훨씬 간단해졌다.

@utility text-shadow-sm {
  text-shadow: 0 1px 2px rgb(0 0 0 / 0.1);
}

@utility text-shadow-md {
  text-shadow: 0 2px 4px rgb(0 0 0 / 0.15);
}

이렇게 정의하면 HTML에서 text-shadow-sm 클래스를 그대로 쓸 수 있다. JavaScript 플러그인 코드를 작성할 필요가 없다.

마이그레이션 실전

1. 자동 마이그레이션 도구

공식 마이그레이션 도구가 제공된다:

npx @tailwindcss/upgrade

이 도구가 해주는 것:

  • tailwind.config.js의 설정을 CSS @theme 블록으로 변환
  • 변경된 유틸리티 이름을 자동 치환
  • import 구조 업데이트
  • PostCSS 설정 정리

완벽하진 않지만 대부분의 변환을 자동으로 처리해준다. 나머지는 수동으로 잡으면 된다.

2. 주의할 점

플러그인 호환성 — v3 플러그인은 v4에서 작동하지 않을 수 있다. @tailwindcss/typography, @tailwindcss/forms 같은 공식 플러그인은 v4용으로 업데이트됐지만, 커뮤니티 플러그인은 확인이 필요하다. 프로젝트에서 쓰고 있는 플러그인 목록을 먼저 점검하자.

PostCSS 설정 변경 — v4는 PostCSS 플러그인 방식 대신 전용 플러그인(@tailwindcss/postcss 또는 @tailwindcss/vite)을 사용한다. 기존 postcss.config.js를 수정해야 한다.

JavaScript 설정에서 하던 복잡한 커스터마이징tailwind.config.js에서 함수를 써서 동적으로 값을 생성하던 패턴은 CSS @theme으로 1:1 변환이 안 될 수 있다. 이 경우 CSS 커스텀 프로퍼티와 @utility 지시어를 조합해서 해결해야 한다.

3. 권장 순서

  1. 프로젝트를 git commit으로 깨끗한 상태로 만든다
  2. npx @tailwindcss/upgrade 실행
  3. 빌드해서 에러 확인
  4. 화면 확인하면서 깨진 스타일 수동 수정
  5. 플러그인 호환성 확인

규모가 큰 프로젝트는 한번에 다 바꾸기보다, 페이지 단위로 점진적으로 확인하는 게 안전하다. 시각적 회귀 테스트 도구(Chromatic, Percy 등)가 있으면 많이 도움 된다.

바꿔야 하나

신규 프로젝트는 v4로 시작하면 된다. 설정이 간소하고 빌드가 빠르고 새 기능이 많다. CSS 안에서 모든 설정을 관리할 수 있다는 건 생각보다 큰 편의성이다.

기존 프로젝트는 급하지 않다. v3도 당분간 유지보수된다. 다만 새 유틸리티(컨테이너 쿼리, 3D 변환 등)를 쓰고 싶거나, 빌드 속도 개선이 절실하다면 마이그레이션할 이유가 충분하다. 자동 마이그레이션 도구가 있어서 생각보다 큰 작업은 아니다. content 배열 관리에서 해방되는 것만으로도 마이그레이션의 가치가 있다.

자주 묻는 질문

v3 프로젝트와 v4 프로젝트를 동시에 유지할 수 있나? 가능하다. 서로 다른 프로젝트에 각각 다른 버전을 쓰면 된다. 같은 프로젝트에서 두 버전을 혼용하는 건 안 되지만, 모노레포에서 패키지별로 다른 버전을 쓰는 건 가능하다.

tailwind.config.js를 완전히 없앨 수 있나? 대부분의 경우 그렇다. 하지만 v3 플러그인 중 아직 v4용으로 마이그레이션되지 않은 것이 있으면 설정 파일이 필요할 수 있다. @config 지시어로 레거시 설정 파일을 참조하는 것도 가능하다.

다크 모드 설정은 어떻게 바뀌었나? v4에서도 dark: 프리픽스는 동일하게 작동한다. 다만 설정 방식이 바뀌었다. v3에서는 tailwind.config.jsdarkMode 옵션으로 설정했는데, v4에서는 CSS의 @variant 지시어를 쓴다. 기본값은 미디어 쿼리 기반이고, 클래스 기반으로 바꾸려면 @variant dark (&:where(.dark, .dark *)); 형태로 정의한다.

기존 커스텀 색상은 어떻게 옮기나? tailwind.config.jstheme.extend.colors에 정의했던 색상을 CSS @theme 블록의 --color-* 변수로 옮기면 된다. 자동 마이그레이션 도구가 이 변환을 처리해주지만, 동적으로 생성하던 색상 값은 수동 작업이 필요하다.

Tailwind 플러그인을 직접 만들어 쓰고 있었는데? v3에서는 JavaScript로 플러그인을 작성했지만, v4에서는 @utility@variant CSS 지시어로 대부분의 커스텀 기능을 구현할 수 있다. 복잡한 로직이 필요한 경우에만 JavaScript 플러그인이 여전히 필요하고, 그 경우에도 v4용 플러그인 API가 제공된다. 다만 v3 플러그인 API와 호환되지 않으니 재작성이 필요하다.

v3에서 v4로의 전환은 단순히 유틸리티 이름이 바뀐 수준이 아니라, Tailwind의 설정 철학 자체가 바뀐 것에 가깝다. JavaScript 중심에서 CSS 중심으로 이동한 거다. 처음에는 낯설 수 있지만, CSS 안에서 모든 걸 관리할 수 있다는 게 적응하면 오히려 편하다.

#TailwindCSS#CSS#프론트엔드#마이그레이션#웹개발

관련 글