Transition and TransitionGroup

Intermediate
12 min

Transition and TransitionGroup

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.

How Transition Works

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.

vue
<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.

Coordinating Two Elements with mode

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:

vue
<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.

Using CSS Animations and Custom Classes

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 }".

JavaScript Hooks

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:

vue
<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.

Animating Lists with TransitionGroup

<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:

vue
<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.

Tips

  • Honor @media (prefers-reduced-motion: reduce) by setting durations to near zero.
  • Keep transitions short (150–400 ms) and animate opacity and transform for GPU-friendly motion.
  • <Transition> requires exactly one child; wrap multiple elements in a single container.
Quick Quiz
Question 1 of 2

Which class is present for the entire duration of an enter transition named `slide`?

Key Takeaways

  • <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.
  • Custom class props integrate third-party CSS libraries; JavaScript hooks with :css="false" integrate animation libraries.
  • <TransitionGroup> animates keyed list items and adds a *-move class for reordering.
  • Keep motion short, prefer 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.

Transition and TransitionGroup - Vue.js | CodeYourCraft | CodeYourCraft