tw-animate-css
Tailwind CSS v4에서 애니메이션을 다루려면 두 가지 문제를 이해해야 한다. 첫째, 기존에 널리 쓰이던 tailwindcss-animate는 JavaScript 플러그인 시스템(@plugin)에 의존하는 구조였는데, Tailwind v4는 CSS-first 아키텍처로 전환하면서 이런 JS 플러그인 방식을 레거시로 취급하기 시작했다. 둘째, Tailwind 자체가 제공하는 빌트인 애니메이션(animate-spin, animate-pulse, animate-bounce, animate-ping)은 종류가 극히 제한적이라 실제 UI에서 필요한 enter/exit 애니메이션, fade, slide, zoom 같은 것들을 표현하기 어렵다.
tw-animate-css는 이 두 문제를 동시에 해결한다. 순수 CSS로 작성된 유틸리티 모음이라 JS 플러그인 없이 @import만으로 동작하고, shadcn/ui가 공식적으로 Tailwind v4 마이그레이션 가이드에서 tailwindcss-animate 대체로 권장하는 패키지다.
tailwindcss-animate와의 차이
tailwindcss-animate는 Tailwind v3의 플러그인 시스템을 활용한다. tailwind.config.js에 플러그인으로 등록하면 JS가 런타임에 유틸리티 클래스를 생성하는 방식이다.
// Tailwind v3 방식 (레거시)
module.exports = {
plugins: [require("tailwindcss-animate")],
};
Tailwind v4에서도 @plugin 지시자로 JS 플러그인을 불러올 수는 있지만, v4가 지향하는 방향은 CSS 레이어 기반의 커스텀 유틸리티다. @theme, @utility, @variant 같은 CSS 지시자로 확장하는 것이 v4의 정석이다.
/* Tailwind v4 방식 */
@import "tailwindcss";
@import "tw-animate-css";
tw-animate-css는 이 철학에 맞게 전체 코드가 CSS로만 구성되어 있다. @theme으로 CSS 변수를 등록하고, @utility로 유틸리티 클래스를 정의하는 구조라 번들러가 CSS만 처리하면 된다. JS 의존성이 없으니 빌드 파이프라인이 단순해지고, tree-shaking 관점에서도 유리하다.
설치와 설정
npm install -D tw-animate-css
설치 후 글로벌 CSS 파일에서 import한다. 순서가 중요한데, Tailwind 자체를 먼저 import하고 그 뒤에 tw-animate-css를 넣어야 한다.
@import "tailwindcss";
@import "tw-animate-css";
이것만으로 설정 끝이다. 별도의 config 파일 수정이 필요 없다. 번들러(Vite, esbuild, Turbopack 등)가 CSS import를 처리할 수 있으면 그대로 동작한다.
핵심 구조: animate-in / animate-out
tw-animate-css의 애니메이션은 base class + transform class 조합으로 동작한다. base class가 애니메이션의 뼈대(keyframe)를 활성화하고, transform class가 구체적인 시각 효과를 지정한다.
<!-- 등장 애니메이션: fade + zoom -->
<div class="animate-in fade-in zoom-in">등장</div>
<!-- 퇴장 애니메이션: fade + slide -->
<div class="animate-out fade-out slide-out-to-bottom">퇴장</div>
animate-in은 요소가 나타날 때, animate-out은 사라질 때의 keyframe을 설정한다. 이 base class 없이 transform class만 붙이면 아무 일도 일어나지 않는다.
내부적으로 animate-in은 대략 이런 keyframe을 활성화한다:
@keyframes enter {
from {
opacity: var(--tw-enter-opacity, 1);
transform: translate3d(
var(--tw-enter-translate-x, 0),
var(--tw-enter-translate-y, 0),
0
)
scale3d(
var(--tw-enter-scale, 1),
var(--tw-enter-scale, 1),
var(--tw-enter-scale, 1)
)
rotate(var(--tw-enter-rotate, 0));
}
}
fade-in, zoom-in, slide-in-from-top 같은 transform class는 이 CSS 변수들의 값을 설정하는 역할이다. fade-in은 --tw-enter-opacity: 0을, zoom-in은 --tw-enter-scale: 0을 설정해서 "어디서부터" 시작할지를 결정한다.
Transform 클래스 상세
Fade (불투명도)
<!-- 기본: opacity 0에서 시작 -->
<div class="animate-in fade-in">...</div>
<!-- 커스텀: opacity 50%에서 시작 -->
<div class="animate-in fade-in-50">...</div>
<!-- 퇴장: opacity 0으로 사라짐 -->
<div class="animate-out fade-out">...</div>
fade-in 뒤의 숫자는 퍼센트 값이다. fade-in-0은 완전 투명에서 시작, fade-in-50은 반투명에서 시작한다. 숫자를 생략하면 기본값 0(완전 투명)이 적용된다.
Zoom (스케일)
<!-- 크기 0에서 확대 등장 -->
<div class="animate-in zoom-in">...</div>
<!-- 75% 크기에서 시작 -->
<div class="animate-in zoom-in-75">...</div>
<!-- 축소되며 퇴장 -->
<div class="animate-out zoom-out">...</div>
zoom-in은 scale(0)에서 scale(1)로의 애니메이션이다. zoom-in-75처럼 값을 지정하면 scale(0.75)에서 시작한다. 모달이나 팝오버가 "톡" 하고 나타나는 효과에 많이 쓴다.
Slide (이동)
<!-- 위에서 아래로 슬라이드 인 -->
<div class="animate-in slide-in-from-top">...</div>
<!-- 왼쪽에서 슬라이드 인 -->
<div class="animate-in slide-in-from-left">...</div>
<!-- 오른쪽으로 슬라이드 아웃 -->
<div class="animate-out slide-out-to-right">...</div>
<!-- 50% 거리에서 슬라이드 인 -->
<div class="animate-in slide-in-from-bottom-4">...</div>
방향은 top, bottom, left, right, start, end 6가지다. start와 end는 RTL 레이아웃을 고려한 논리적 방향으로, LTR에서 start = left, end = right이 된다. 기본 이동 거리는 100%이고, 뒤에 숫자를 붙여 조절할 수 있다.
Spin (회전)
<!-- 기본: 30도 회전하며 등장 -->
<div class="animate-in spin-in">...</div>
<!-- 180도 회전 -->
<div class="animate-in spin-in-180">...</div>
기본값은 30도다. 아이콘이나 작은 요소에 살짝 회전 효과를 줄 때 유용하다.
Blur (블러)
<!-- 블러 상태에서 선명해지며 등장 -->
<div class="animate-in blur-in">...</div>
<!-- 커스텀 블러 강도 -->
<div class="animate-in blur-in-md">...</div>
<!-- 퇴장 시 블러 -->
<div class="animate-out blur-out">...</div>
blur-in은 흐린 상태에서 선명해지는 효과다. Tailwind의 blur 스케일(sm, md, lg 등)을 그대로 사용할 수 있다.
파라미터 클래스
애니메이션의 동작 방식을 제어하는 클래스들이다. CSS animation-* 속성에 대응한다.
duration (지속 시간)
<!-- 300ms -->
<div class="animate-in fade-in duration-300">...</div>
<!-- 1초 -->
<div class="animate-in fade-in duration-1000">...</div>
Tailwind의 duration-* 유틸리티를 그대로 사용한다. 기본값은 150ms인데, UI 애니메이션에서는 200~300ms가 자연스러운 경우가 많다.
ease (타이밍 함수)
<div class="animate-in fade-in ease-in-out">...</div>
<div class="animate-in fade-in ease-linear">...</div>
기본값은 CSS의 ease 함수다. ease-in은 느리게 시작, ease-out은 느리게 끝남, ease-in-out은 양쪽 다 느림. 커스텀 베지어도 가능하다:
<div class="animate-in fade-in ease-[cubic-bezier(0.4,0,0.2,1)]">...</div>
delay (지연)
<div class="animate-in fade-in delay-150">...</div>
<div class="animate-in fade-in delay-500">...</div>
여러 요소에 순차적으로 애니메이션을 적용할 때(staggered animation) 유용하다.
repeat (반복)
<!-- 3번 반복 -->
<div class="animate-in fade-in repeat-3">...</div>
<!-- 무한 반복 -->
<div class="animate-in fade-in repeat-infinite">...</div>
direction (방향)
<!-- 정방향 → 역방향 교대 -->
<div class="animate-in fade-in direction-alternate">...</div>
<!-- 역방향 -->
<div class="animate-in fade-in direction-reverse">...</div>
repeat과 direction-alternate를 조합하면 요소가 나타났다 사라졌다를 반복하는 효과를 만들 수 있다.
fill-mode (종료 상태)
<!-- 애니메이션 종료 후 최종 상태 유지 -->
<div class="animate-in fade-in fill-mode-forwards">...</div>
<!-- 애니메이션 시작 전 초기 상태 적용 -->
<div class="animate-in fade-in fill-mode-backwards">...</div>
<!-- 양쪽 다 -->
<div class="animate-in fade-in fill-mode-both">...</div>
fill-mode-forwards는 애니메이션이 끝난 뒤 마지막 프레임 상태를 유지한다. exit 애니메이션에서 요소가 사라진 상태를 유지해야 할 때 필수다.
play-state (재생 상태)
<div class="animate-in fade-in paused">...</div>
<div class="animate-in fade-in running">...</div>
프로그래밍 방식으로 애니메이션을 일시정지/재개할 때 사용한다.
내장 애니메이션
enter/exit 조합 외에도 바로 사용할 수 있는 내장 애니메이션이 있다.
<!-- 아코디언 열기/닫기 -->
<div class="animate-accordion-down">...</div>
<div class="animate-accordion-up">...</div>
<!-- 커서 깜빡임 -->
<span class="animate-caret-blink">|</span>
shadcn/ui의 Accordion 컴포넌트가 내부적으로 animate-accordion-down/animate-accordion-up을 사용한다. 이 애니메이션은 height: 0에서 height: var(--radix-accordion-content-height)로 전환하는 방식이라, Radix UI의 Accordion 컴포넌트와 자연스럽게 연동된다.
조합 패턴
transform 클래스는 여러 개를 동시에 사용할 수 있다. 이것이 tw-animate-css가 강력한 이유다.
<!-- fade + zoom: 불투명+작은 상태에서 등장 -->
<div class="animate-in fade-in zoom-in-95 duration-200">
모달
</div>
<!-- fade + slide: 아래에서 올라오며 등장 -->
<div class="animate-in fade-in slide-in-from-bottom-4 duration-300">
토스트
</div>
<!-- fade + zoom + slide: 풀 콤보 -->
<div class="animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200">
드롭다운
</div>
shadcn/ui 컴포넌트에서 자주 보이는 패턴들이다:
| 컴포넌트 | 등장 | 퇴장 |
|---|---|---|
| Dialog | fade-in-0 zoom-in-95 | fade-out-0 zoom-out-95 |
| Dropdown | fade-in-0 zoom-in-95 | fade-out-0 zoom-out-95 |
| Toast | fade-in-0 slide-in-from-bottom-4 | fade-out-0 slide-out-to-right |
| Sheet (right) | slide-in-from-right | slide-out-to-right |
| Tooltip | fade-in-0 zoom-in-95 | fade-out-0 zoom-out-95 |
Tailwind v4 상태 기반 애니메이션
Tailwind의 variant 시스템과 조합하면 상태에 따라 애니메이션을 적용할 수 있다.
<!-- data 속성 기반 (Radix UI 패턴) -->
<div class="data-[state=open]:animate-in data-[state=open]:fade-in
data-[state=closed]:animate-out data-[state=closed]:fade-out">
...
</div>
<!-- 그룹 hover -->
<div class="group">
<div class="group-hover:animate-in group-hover:slide-in-from-bottom">
...
</div>
</div>
Radix UI는 컴포넌트의 열림/닫힘 상태를 data-state="open" / data-state="closed" 속성으로 노출한다. 이걸 Tailwind의 data-[state=open]: variant와 결합하면 CSS만으로 상태 기반 애니메이션을 선언적으로 표현할 수 있다.
CSS 변수로 커스터마이징
tw-animate-css가 사용하는 CSS 변수를 직접 오버라이드해서 세밀한 제어가 가능하다.
/* 전역 기본값 변경 */
:root {
--tw-animate-duration: 300ms;
--tw-animate-ease: cubic-bezier(0.4, 0, 0.2, 1);
}
특정 컴포넌트에만 다른 설정을 적용하려면 해당 요소에 CSS 변수를 지정하면 된다:
.my-modal {
--tw-enter-opacity: 0;
--tw-enter-scale: 0.95;
--tw-exit-opacity: 0;
--tw-exit-scale: 0.95;
}
tailwindcss-animate에서 마이그레이션
기존 tailwindcss-animate를 쓰고 있다면 전환은 간단하다.
/* globals.css */
@import "tailwindcss";
- @plugin "tailwindcss-animate";
+ @import "tw-animate-css";
대부분의 클래스명이 호환되지만 몇 가지 차이가 있다:
| tailwindcss-animate | tw-animate-css | 비고 |
|---|---|---|
animate-in | animate-in | 동일 |
fade-in | fade-in | 동일 |
slide-in-from-top | slide-in-from-top | 동일 |
| JS 플러그인 | 순수 CSS | 아키텍처 변경 |
클래스명 레벨에서는 거의 1:1 호환이 되기 때문에, import 경로만 바꾸면 기존 코드가 대부분 그대로 동작한다.
정리
tw-animate-css는 "Tailwind v4에서 UI 애니메이션을 어떻게 해야 하는가"에 대한 현재 기준의 정답이다. shadcn/ui 생태계의 공식 권장 패키지이고, 순수 CSS 기반이라 Tailwind v4의 설계 철학에 부합하며, animate-in/animate-out + transform 클래스 조합이라는 직관적인 API를 제공한다. 기존 tailwindcss-animate와 클래스명이 호환되어 마이그레이션 비용도 낮다.
관련 문서
- 상태 기반 스타일링
- Radix UI
- Framer Motion shadcn/ui 생태계의 공식 권장 패키지이고, 순수 CSS 기반이라 Tailwind v4의 설계 철학에 부합하며,
animate-in/animate-out+ transform 클래스 조합이라는 직관적인 API를 제공한다. 기존tailwindcss-animate와 클래스명이 호환되어 마이그레이션 비용도 낮다.