Vue.js: Transitions & Animations
Last updated: 2026-10-20
Transitions and animations make your Vue app feel more alive—elements enter and leave smoothly, and lists reorder gracefully when they change. Vue ships with the built-in <Transition> and <TransitionGroup> components, which work with CSS class names or JavaScript hooks to produce a wide range of animation effects.
Mastering transitions is the key to a better user experience—good animation makes an app feel "fluid, responsive, and professional."
1. What You'll Learn
<Transition>enter/leave animations for a single element- The 6 CSS class hooks (v-enter-from / v-enter-active, etc.)
<TransitionGroup>list animations- JavaScript hooks (@before-enter / @enter, etc.)
- Third-party animation library integration (@vueuse/motion, GSAP)
- Animate.css integration
- 5 real-world scenarios
2. The Awkward "Hard-Cut Tab Switch"
(1) Pain Point: Components Vanish and Reappear, the UI Flickers
Alice's admin dashboard has a tab switcher:
User experience
- When the tab changes, content appears and disappears abruptly
- There is no transition at all, so it feels "stiff"
- It doesn't feel professional
(2) The Vue Transition Fix: 1 Tag, 6 Classes
User experience
- Tab switches now fade in and out smoothly
- A 0.3-second transition that feels polished
- The 6 CSS classes are applied automatically
(3) Benefits
After adding the transition:
- User experience: from "stiff" to "smooth"
- Professional feel: noticeably better
- Code volume: 1
<Transition>tag + 6 CSS classes - Customizable: 4 transition names + 3 modes
3. Single-Element Transition
(1) Basic Usage
(2) The 6 CSS Classes Explained
| Class | When it fires | Purpose |
|---|---|---|
v-enter-from |
Before the element is inserted | Starting state |
v-enter-active |
Throughout the insert | Transition in progress |
v-enter-to |
After the element is inserted | Ending state |
v-leave-from |
Before the element leaves | Starting state |
v-leave-active |
Throughout the leave | Transition in progress |
v-leave-to |
After the element leaves | Ending state |
(3) Naming Rules
<!-- v-bind:name="fade" → the 6 classes are .fade-xxx -->
<Transition name="fade">...</Transition>
<!-- No name → defaults to v-xxx (the 6 classes are .v-xxx) -->
<Transition>...</Transition>
<!-- Dynamic name -->
<Transition :name="transitionName">...</Transition>
(4) The 3 Modes
<!-- 1. Default mode: enter and leave happen at the same time (overlap) -->
<Transition name="fade">
<div v-if="show">Content</div>
</Transition>
<!-- 2. out-in: leave first, then enter (recommended, avoids overlap) -->
<Transition name="fade" mode="out-in">
<div v-if="show">Content</div>
</Transition>
<!-- 3. in-out: enter first, then leave (rarely used) -->
<Transition name="fade" mode="in-out">
<div v-if="show">Content</div>
</Transition>
▶ Example: Default Mode vs mode="out-in", Side by Side
▶ Try it YourselfThe same pair of
v-ifcards twice: the top one with no mode (enter and leave overlap), the bottom one withmode="out-in"(leave finishes before enter starts). Toggle and the difference is obvious.
Output:
Both areas start on the green "Card A", and the button reads "Switch card (current A)".
After one click, the top area fades the old card out and the new one in at the same time — green and blue briefly overlap and the container height jumps.
The bottom `mode="out-in"` area waits ~0.5s for the green card to leave, then fades the blue "Card B" in; only one card is ever visible.
Clicking again switches back to A with the same visible difference.
4. 5 CSS Animations
(1) Fade (most common)
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
(2) Slide
.slide-enter-active, .slide-leave-active {
transition: transform 0.3s;
}
.slide-enter-from {
transform: translateX(-100%);
}
.slide-leave-to {
transform: translateX(100%);
}
(3) Scale
.scale-enter-active, .scale-leave-active {
transition: transform 0.3s;
}
.scale-enter-from, .scale-leave-to {
transform: scale(0);
}
(4) Rotate
.rotate-enter-active {
transition: transform 1s;
}
.rotate-enter-from {
transform: rotate(0deg);
}
.rotate-leave-active {
animation: bounce 0.5s;
}
@keyframes bounce {
0% { transform: scale(0); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
(5) Combined
.combo-enter-active, .combo-leave-active {
transition: all 0.3s;
}
.combo-enter-from {
opacity: 0;
transform: translateX(-50px) scale(0.8);
}
.combo-leave-to {
opacity: 0;
transform: translateX(50px) scale(0.8);
}
5. TransitionGroup List Animations
(1) Basic Usage
(2) Key Point: .list-move
/* Applied automatically when an item changes position (e.g. reordering) */
.list-move {
transition: transform 0.5s;
}
(3) 5 List Animation Scenarios
| Scenario | Animation |
|---|---|
| Adding an item | Fade in + slide down |
| Removing an item | Fade out + slide left |
| Reordering | Smooth move to the new position |
| Filtering | Leaving items fade out, remaining items transition smoothly |
| Dragging | Follows the mouse in real time |
▶ Example: Adding, Removing, and Reordering with TransitionGroup
▶ Try it Yourself"Add item" shows the enter animation, clicking a row's × shows the leave animation with the other rows sliding up, and "Move first to bottom" shows
.list-movesliding whole rows.
Output:
The list starts with 3 rows: Vue / Router / Pinia, each with a red × on the right.
"Add an item" fades the new row "Item 4" in and slides it from the right (0.5s); clicking again appends "Item 5".
Clicking a row's × fades that row out while the remaining rows slide up smoothly within 0.5s instead of jumping.
"Move first to bottom" glides the first row down to the end while the others shift up — a continuous motion, no teleporting.
6. JavaScript Hooks
(1) The 8 JS Hooks
<template>
<Transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
:css="false" <!-- disable CSS classes -->
>
<p v-if="show">Hello</p>
</Transition>
</template>
<script setup>
import gsap from 'gsap'
function beforeEnter(el) {
console.log('Before enter:', el)
}
function enter(el, done) {
gsap.to(el, {
opacity: 1,
y: 0,
duration: 0.5,
onComplete: done // signal that the transition is done
})
}
function afterEnter(el) {
console.log('After enter:', el)
}
// leave / beforeLeave / afterLeave work the same way
</script>
(2) GSAP Integration (recommended)
npm install gsap
<template>
<Transition :css="false" @enter="onEnter" @leave="onLeave">
<p v-if="show">Hello GSAP</p>
</Transition>
</template>
<script setup>
import gsap from 'gsap'
function onEnter(el, done) {
gsap.fromTo(el,
{ opacity: 0, y: 50 },
{ opacity: 1, y: 0, duration: 0.5, onComplete: done }
)
}
function onLeave(el, done) {
gsap.to(el, { opacity: 0, y: -50, duration: 0.3, onComplete: done })
}
</script>
7. Third-Party Animation Libraries
(1) Animate.css Integration
npm install animate.css
// main.js
import 'animate.css'
(2) @vueuse/motion
npm install @vueuse/motion
// main.js
import { MotionPlugin } from '@vueuse/motion'
app.use(MotionPlugin)
(3) When to Use GSAP
| Library | Rating | Best for |
|---|---|---|
| CSS animations | ⭐⭐⭐⭐⭐ | Simple transitions, fades |
| Animate.css | ⭐⭐⭐⭐ | A large set of preset animations |
| @vueuse/motion | ⭐⭐⭐⭐ | Declarative, well integrated with Vue |
| GSAP | ⭐⭐⭐⭐⭐ | Complex timelines, physics-based animation |
8. Putting It Together: All 5 Transitions in One Demo
▶ Example: 5 Vue Transition Animations (fully supported via CDN)
▶ Expand runnable example (87 lines)
Output:
Five demo blocks render stacked down the page.
Clicking a button starts a 0.3s animation instead of an instant swap: the greeting fades out, the slide paragraph moves off to the right edge, the modal scales up from 0 over a dark backdrop, added list items fade in and shifted items glide to their new position, and the tab panel uses the combo animation because of mode="out-in".
┌────────────────────────────────────────────────┐
│ 1. Fade in / fade out │
│ [Hide] 👋 Hello Fade │
│ 2. Slide │
│ 👉 Slide in │
│ [Toggle] │
│ 3. Scale (modal) │
│ [Open modal] │
│ 4. TransitionGroup list animation │
│ [New item ] [Add] │
│ • Item 1 × │
│ • Item 2 × │
│ 5. Combo (tab switching) │
│ [Home] [Profile] [Settings] │
│ Current tab: Home │
└────────────────────────────────────────────────┘
Each interaction animates over 0.3s (0.5s for list items)
instead of switching instantly.
(1) 📊 The 6 CSS Classes in Detail
| Class | Trigger |
|---|---|
.fade-enter-from |
Before the element is inserted |
.fade-enter-active |
During the insert |
.fade-enter-to |
After the element is inserted |
.fade-leave-from |
Before the element leaves |
.fade-leave-active |
During the leave |
.fade-leave-to |
After the element leaves |
(2) 📊 5 Major Third-Party Animation Libraries (⚠️ requires Vite)
| Library | Command | Best for |
|---|---|---|
| CSS | None (built in) | Simple cases |
| Animate.css | npm i animate.css |
Preset animations |
| @vueuse/motion | npm i @vueuse/motion |
Declarative usage |
| GSAP | npm i gsap |
Complex timelines |
| Motion One | npm i motion |
Lightweight web animation |
(3) 📊 Quick Reference for 5 Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| Transition has no effect | The element just disappears | Wrap it in <Transition> together with v-if |
| v-for items don't animate | List items have no transition | Use <TransitionGroup> |
| Reordering isn't animated | Items jump abruptly | Add the .list-move class |
| Several components switch at once | They overlap | Add a distinct key + mode="out-in" |
| CSS animation too fast/slow | Poor experience | Tune the transition duration to 0.2–0.5s |
(4) 📊 5 Real-World Scenarios
| Scenario | Animation |
|---|---|
| Tab switching | fade + out-in |
| Modal dialog | scale + fade |
| List add/remove | list animation |
| Route changes | fade + slide |
| Toast notifications | slide from top + auto-dismiss |
❓ FAQ
<TransitionGroup> must have a unique :key, otherwise the animation won't fire.<Transition> using <router-view v-slot="{ Component }">. Each route change swaps the Component, which triggers the Transition.<Transition :css="false" @enter="onEnter"> disables the CSS classes and relies only on JS hooks. Or enable both (CSS classes for styling, JS hooks for callbacks).enter-active-class="animate__animated animate__fadeIn" to map Animate.css class names onto Vue's hooks.📖 Summary
<Transition>handles a single element's enter/leave, with 6 CSS classes managed automatically<TransitionGroup>handles list animation, and.list-movehandles reordering- 3 modes: default (overlap) / out-in (recommended) / in-out
- 5 CSS animations: fade / slide / scale / rotate / combined
- 8 JS hooks: before-enter / enter / after-enter / before-leave / leave / after-leave
- 5 third-party libraries: CSS / Animate.css / @vueuse/motion / GSAP / Motion One
- 5 real-world use cases: tabs / modal / lists / routing / notifications
📝 Exercises
-
Basic (difficulty ⭐) Build a simple fade-in/fade-out modal:
- Clicking the button opens the modal
- It fades in over 0.3 seconds
- Clicking close fades it out
- The backdrop fades in and out in sync
-
Intermediate (difficulty ⭐⭐) Build a TransitionGroup list animation:
- 5 todo items
- New items fade in (0.5s)
- Removed items fade out (0.5s +
position: absolute) - Remaining items glide to their new position when reordered (
.list-move)
-
Challenge (difficulty ⭐⭐⭐) Build a complete "page transition" system:
- fade + slide on route changes
- 3 animations for list add/remove/reorder
- scale + fade combo for modals
- a complex timeline animation integrated with GSAP
- a side-by-side comparison of 5 third-party animation libraries
- performance optimization (GPU acceleration + will-change)