Vue.js Transitions and Animations
Vue has a built-in system for animating elements when they are added to or removed from the page. The <Transition> and <TransitionGroup> components handle the timing, and you write the visual effect with CSS.
How Vue Transitions Work
When an element inside <Transition> appears or disappears, Vue temporarily adds special CSS classes at each stage of the animation. You write CSS rules for those classes to create the visual effect.
Diagram: Transition Class Stages
ENTERING (element appears): Stage 1: v-enter-from → starting state (e.g., opacity: 0) Stage 2: v-enter-active → transition is running (e.g., transition: opacity 0.3s) Stage 3: v-enter-to → ending state (e.g., opacity: 1) LEAVING (element disappears): Stage 1: v-leave-from → starting state (e.g., opacity: 1) Stage 2: v-leave-active → transition is running (e.g., transition: opacity 0.3s) Stage 3: v-leave-to → ending state (e.g., opacity: 0) Vue adds each class at the right moment and removes it when done.
Basic Fade Transition
<div id="app">
<button @click="show = !show">Toggle Message</button>
<Transition>
<p v-if="show">Hello! I fade in and out.</p>
</Transition>
</div>
<style>
.v-enter-active,
.v-leave-active {
transition: opacity 0.4s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
<script>
Vue.createApp({
data() { return { show: true }; }
}).mount("#app");
</script>
Diagram: Fade Effect Timeline
User clicks "Toggle" (show becomes false):
Time 0ms: v-leave-from added → opacity: 1 (normal)
v-leave-active added → transition: opacity 0.4s
Time 0ms+: v-leave-to added → opacity: 0 (fading out)
Time 400ms: transition complete → element removed from DOM
User clicks again (show becomes true):
Time 0ms: v-enter-from added → opacity: 0 (invisible)
v-enter-active added → transition: opacity 0.4s
Time 0ms+: v-enter-to added → opacity: 1 (fading in)
Time 400ms: transition complete → classes removed
Named Transitions
Give a transition a name using the name prop. Vue uses that name instead of v as the class prefix. This lets you define multiple different transitions on the same page.
<Transition name="slide">
<p v-if="show">I slide in from the left.</p>
</Transition>
<style>
/* Classes are now: slide-enter-from, slide-enter-active, etc. */
.slide-enter-active,
.slide-leave-active {
transition: transform 0.4s ease, opacity 0.4s ease;
}
.slide-enter-from {
transform: translateX(-40px);
opacity: 0;
}
.slide-leave-to {
transform: translateX(40px);
opacity: 0;
}
</style>
Diagram: Named Transition Class Prefix
<Transition> (no name):
Classes: v-enter-from, v-enter-active, v-enter-to
v-leave-from, v-leave-active, v-leave-to
<Transition name="slide">:
Classes: slide-enter-from, slide-enter-active, slide-enter-to
slide-leave-from, slide-leave-active, slide-leave-to
<Transition name="bounce">:
Classes: bounce-enter-from, bounce-enter-active, bounce-enter-to
bounce-leave-from, bounce-leave-active, bounce-leave-to
CSS Animation — Using @keyframes
You can use CSS @keyframes animations instead of transitions. Apply the animation in the -active class.
<Transition name="pop">
<div v-if="show" class="badge">New!</div>
</Transition>
<style>
.pop-enter-active {
animation: pop-in 0.4s ease;
}
.pop-leave-active {
animation: pop-in 0.3s ease reverse;
}
@keyframes pop-in {
0% { transform: scale(0); opacity: 0; }
70% { transform: scale(1.2); }
100% { transform: scale(1); opacity: 1; }
}
</style>
Transition Modes
When you switch between two elements (using v-if / v-else), both the entering and leaving transitions run at the same time by default. The mode prop controls the order.
<!-- Both transitions run at the same time (default) --> <Transition> <button v-if="editing">Save</button> <button v-else>Edit</button> </Transition> <!-- Old element leaves first, then new one enters --> <Transition name="fade" mode="out-in"> <button v-if="editing">Save</button> <button v-else>Edit</button> </Transition> <!-- New element enters first, then old one leaves --> <Transition name="fade" mode="in-out"> ... </Transition>
Diagram: out-in Mode
Without mode (simultaneous):
"Edit" fades out ──────────────▶ done
"Save" fades in ──────────────▶ done
(both happen at the same time — can look jumpy)
mode="out-in" (sequential):
"Edit" fades out ──────────────▶ done
│
▼
"Save" fades in ──────▶ done
(clean, one at a time)
TransitionGroup — Animating Lists
<TransitionGroup> animates multiple elements — like a list — when items are added, removed, or reordered. Unlike <Transition>, it renders a real DOM element (default: <span>) or one you specify with the tag prop.
<div id="app">
<button @click="addItem">Add Item</button>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</TransitionGroup>
</div>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.4s ease;
}
.list-enter-from {
opacity: 0;
transform: translateX(-30px);
}
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* Smooth movement when items shift positions */
.list-move {
transition: transform 0.4s ease;
}
</style>
Diagram: TransitionGroup Item Lifecycle
List: [Item A, Item B, Item C] Add Item D: Item D: enter-from (invisible, shifted left) → enter-active (transition runs) → enter-to (fully visible) [Item A, Item B, Item C, Item D] Remove Item B: Item B: leave-from (visible) → leave-active (transition runs) → leave-to (invisible, shifted right) Item C moves up: .list-move handles smooth repositioning [Item A, Item C, Item D]
Appear — Animate on First Load
By default, the transition does not run when the component first loads. Add the appear prop to animate the element on its initial render.
<Transition name="fade" appear>
<div class="hero-banner">
Welcome to our site!
</div>
</Transition>
JavaScript Hooks for Complex Animations
For animations that CSS cannot easily handle — physics, dynamic values, third-party libraries — Vue provides JavaScript lifecycle hooks on the <Transition> component.
<Transition
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<div v-if="show">Animated element</div>
</Transition>
<script>
methods: {
beforeEnter(el) {
el.style.opacity = 0;
},
enter(el, done) {
// Use a library like GSAP here
el.style.transition = "opacity 0.5s";
el.style.opacity = 1;
setTimeout(done, 500);
},
leave(el, done) {
el.style.transition = "opacity 0.5s";
el.style.opacity = 0;
setTimeout(done, 500);
}
}
</script>
Summary
The <Transition> component wraps a single element and applies CSS classes at six stages of its enter and leave animation. Name the transition to use a custom class prefix and define multiple distinct animations. Use mode="out-in" for clean sequential switching between two elements. <TransitionGroup> handles animated lists, including smooth repositioning with the -move class. Add appear for on-load animations, and use JavaScript hooks for advanced animation scenarios that go beyond CSS.
