Elements that appear and disappear abruptly feel broken; a short fade or slide tells the user what changed. Vue's <Transition> applies CSS classes at the right moments when an element is inserted or removed, and <TransitionGroup> does the same for every item of a list, including items that move. After this lesson you will animate v-if/v-show toggles, coordinate two elements swapping, and animate list changes.
Wrap a single element or component that is toggled by v-if, v-show or <component :is>. Vue adds six classes, prefixed by the name prop, during the enter and leave phases:
| Phase | Start frame | Whole phase | End frame |
| --- | --- | --- | --- |
| Enter | fade-enter-from | fade-enter-active | fade-enter-to |
| Leave | fade-leave-from | fade-leave-active | fade-leave-to |
Vue detects the CSS transition or animation duration on the element and removes the classes when it ends, so nothing is left behind.
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<template>
<button @click="show = !show">Toggle</button>
<Transition name="fade">
<p v-if="show">Hello</p>
</Transition>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>Without a name, the classes default to v-enter-from, v-enter-active and so on. Add the appear prop to also run the enter transition on the initial render.
When one element leaves as another enters (a v-if/v-else pair or a dynamic component), both animate at the same time by default and briefly overlap. The mode prop serializes them:
<template>
<Transition name="fade" mode="out-in">
<component :is="currentTab" :key="currentTab.name" />
</Transition>
</template>out-in finishes the leave before starting the enter (almost always what you want); in-out does the reverse. When toggling between elements of the same tag, give each a distinct key so Vue treats them as different elements rather than patching one in place.
Keyframe animations work as well as transitions; put the animation on the *-enter-active and *-leave-active classes. To integrate a library such as Animate.css, override the class names with props: enter-active-class="animate__animated animate__fadeInDown" and leave-active-class="animate__animated animate__fadeOutUp". If an element has both a transition and an animation with different durations, tell Vue which to watch with type="animation", or set an explicit :duration="{ enter: 500, leave: 300 }".
For animations driven by JavaScript (GSAP, the Web Animations API, or measuring heights), listen to the transition events. Set :css="false" so Vue skips class detection and waits for your done callback:
<template>
<Transition :css="false" @before-enter="onBeforeEnter" @enter="onEnter" @leave="onLeave">
<div v-if="show" class="panel">Details</div>
</Transition>
</template>
<script setup>
function onBeforeEnter(el) { el.style.height = '0' }
function onEnter(el, done) {
el.animate([{ height: '0' }, { height: `${el.scrollHeight}px` }], { duration: 250 })
.finished.then(done)
}
function onLeave(el, done) {
el.animate([{ height: `${el.scrollHeight}px` }, { height: '0' }], { duration: 250 })
.finished.then(done)
}
</script>after-enter, after-leave and the *-cancelled variants complete the hook set.
<TransitionGroup> animates each child of a v-for. It renders a wrapper element given by tag (or none if omitted), every child needs a unique key, and items that change position get a *-move class:
<template>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</TransitionGroup>
</template>
<style>
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.4s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(20px);
}
.list-leave-active {
position: absolute;
}
</style>The position: absolute rule on leaving items takes them out of the flow so the remaining items slide smoothly into place. Vue uses the FLIP technique for the move transition, so reordering and filtering animate correctly.
@media (prefers-reduced-motion: reduce) by setting durations to near zero.opacity and transform for GPU-friendly motion.<Transition> requires exactly one child; wrap multiple elements in a single container.Which class is present for the entire duration of an enter transition named `slide`?
<Transition> toggles *-enter-from/active/to and *-leave-from/active/to classes around v-if, v-show and dynamic components.mode="out-in" sequences a leaving and an entering element; use distinct keys for same-tag elements.:css="false" integrate animation libraries.<TransitionGroup> animates keyed list items and adds a *-move class for reordering.opacity and transform, and honor prefers-reduced-motion.Next lesson: Custom Directives and Plugins — extend Vue with your own v-* directives and package app-wide functionality as plugins.