Vue's built-in <Transition> component applies enter/leave animations to a single element or component. It automatically adds CSS classes at the right moments, or you can use JavaScript hooks for full control.
| Class | When applied |
|---|---|
| v-enter-from | Start state of enter - added before element is inserted |
| v-enter-active | Active state of enter - applied during entire enter phase |
| v-enter-to | End state of enter - added after element is inserted |
| v-leave-from | Start state of leave |
| v-leave-active | Active state of leave |
| v-leave-to | End state of leave |
<template>
<div>
<button @click="show = !show">Toggle</button>
<!-- Basic fade transition -->
<Transition name="fade">
<p v-if="show">Hello, I fade in and out!</p>
</Transition>
<!-- Slide transition -->
<Transition name="slide">
<div v-if="show" class="panel">Sliding panel</div>
</Transition>
<!-- Mode: out-in (leave first, then enter) -->
<Transition name="fade" mode="out-in">
<component :is="currentView" :key="currentView" />
</Transition>
<!-- appear: animate on initial render -->
<Transition name="fade" appear>
<p>I animate when the page loads</p>
</Transition>
</div>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
const currentView = ref('HomeView')
</script>
<style>
/* Fade transition */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* Slide transition */
.slide-enter-active {
transition: all 0.3s ease-out;
}
.slide-leave-active {
transition: all 0.3s ease-in;
}
.slide-enter-from {
transform: translateX(-100%);
opacity: 0;
}
.slide-leave-to {
transform: translateX(100%);
opacity: 0;
}
/* Scale + fade */
.scale-enter-active,
.scale-leave-active {
transition: all 0.25s ease;
}
.scale-enter-from,
.scale-leave-to {
transform: scale(0.9);
opacity: 0;
}
</style>
<!-- TransitionGroup - animate lists -->
<template>
<div>
<input v-model="newItem" @keyup.enter="addItem" placeholder="Add item" />
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item.id">
{{ item.text }}
<button @click="removeItem(item.id)">x</button>
</li>
</TransitionGroup>
</div>
</template>
<script setup>
import { ref } from 'vue'
const newItem = ref('')
const items = ref([
{ id: 1, text: 'Learn Vue' },
{ id: 2, text: 'Build something' },
])
function addItem() {
if (!newItem.value.trim()) return
items.value.push({ id: Date.now(), text: newItem.value })
newItem.value = ''
}
function removeItem(id) {
items.value = items.value.filter(i => i.id !== id)
}
</script>
<style>
/* List item enter/leave */
.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 repositioning of remaining items */
.list-move {
transition: transform 0.4s ease;
}
/* Prevent layout shift during leave */
.list-leave-active {
position: absolute;
}
</style>
<!-- JavaScript hooks - full control with GSAP or Web Animations API -->
<template>
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
:css="false"
>
<div v-if="show" class="box">Animated box</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
// :css="false" - disable CSS transitions, use JS only
function onBeforeEnter(el) {
el.style.opacity = '0'
el.style.transform = 'scale(0.8)'
}
function onEnter(el, done) {
// Use Web Animations API
el.animate([
{ opacity: 0, transform: 'scale(0.8)' },
{ opacity: 1, transform: 'scale(1)' }
], {
duration: 400,
easing: 'ease-out'
}).onfinish = done // call done() when animation completes
}
function onLeave(el, done) {
el.animate([
{ opacity: 1, transform: 'scale(1)' },
{ opacity: 0, transform: 'scale(0.8)' }
], {
duration: 300,
easing: 'ease-in'
}).onfinish = done
}
function onAfterEnter(el) { console.log('Enter complete') }
function onAfterLeave(el) { console.log('Leave complete') }
function onBeforeLeave(el) { /* ... */ }
function onEnterCancelled(el) { /* ... */ }
</script>
Transition applies from, active, and to classes at defined moments around insertion and removal. The active class usually owns duration and easing, while the from and to classes define endpoints. Inspect the class changes in developer tools when an element jumps directly to its final state.
Use stable keys when Transition switches between elements so Vue recognizes replacement rather than patching one node. A mode can sequence leaving and entering when both elements must not occupy the layout together. TransitionGroup animates keyed list insertion, removal, and movement but does not repair unstable identity.
JavaScript transitions receive lifecycle hooks and may require an explicit done callback. Call it exactly once after the animation completes, and cancel outstanding animation work when the element leaves or the component unmounts. Honor reduced-motion preferences and keep the final DOM state correct when animation is disabled.
A transition needs Vue to insert, remove, show, or hide the element it wraps. If the element is only changed by unrelated CSS, <Transition> has no enter or leave phase to observe.
TransitionGroup relies on stable keys to know which logical item moved, entered, or left. If a v-for list uses the array index as the key, filtering or sorting changes indexes for existing items, so Vue may reuse the wrong DOM node.
Use v-show when the same element toggles frequently and should remain mounted, such as a menu or dropdown. Vue changes display, so local DOM state and refs remain intact.
Explore 500+ free tutorials across 20+ languages and frameworks.