Suggest an editImprove this articleRefine the answer for “Transition and transitiongroup in Vue.js”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Vue Transition** - a built-in component that wraps a single element and applies CSS animation classes when it enters or leaves the DOM. `<TransitionGroup>` extends this to lists and handles reorder animations. ```vue <Transition name="fade"> <p v-if="show">Hello!</p> </Transition> ``` **Key:** Vue injects `fade-enter-from`, `fade-enter-active`, and `fade-enter-to` on enter; the matching `leave` variants on exit. Use `mode="out-in"` for component swaps and `:css="false"` when using GSAP or similar libraries.Shown above the full answer for quick recall.Answer (EN)Image**Vue Transition** - a built-in component that wraps a single element and applies CSS classes at the right moment so enter and leave animations work without any extra library. ## Theory ### TL;DR - `<Transition>` animates one element; `<TransitionGroup>` animates a `v-for` list - Vue injects six CSS classes automatically: `v-enter-from`, `v-enter-active`, `v-enter-to` on enter, and the same `leave` variants on exit - Set `name="fade"` and the `v-` prefix becomes `fade-` - `mode="out-in"` prevents two components from overlapping during a swap - JavaScript hooks (`@enter`, `@leave`) let you use GSAP or any animation library when CSS is not enough ### Quick example ```vue <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; /* hidden at start of enter and end of leave */ } </style> ``` Vue adds `.fade-enter-from` and `.fade-enter-active` the frame the `<p>` enters the DOM. One frame later it adds `.fade-enter-to` and removes `enter-from`. When the transition ends, it cleans up all classes. The same flow runs in reverse on leave. ### CSS class flow Six classes follow a fixed sequence. The prefix is `v-` by default, or whatever you pass to `name`: ``` ENTER: v-enter-from → v-enter-active → v-enter-to LEAVE: v-leave-from → v-leave-active → v-leave-to ``` | Class | Applied when | |---|---| | `fade-enter-from` | First frame of enter (start state) | | `fade-enter-active` | Entire enter duration | | `fade-enter-to` | Last frame of enter (end state) | | `fade-leave-from` | First frame of leave (start state) | | `fade-leave-active` | Entire leave duration | | `fade-leave-to` | Last frame of leave (end state) | The `-active` class is where you write your `transition` or `animation` CSS. The `-from` and `-to` classes define the start and end states. That split lets you mix CSS transitions and CSS animations freely. ### TransitionGroup for lists `<TransitionGroup>` adds one thing that `<Transition>` cannot do: a move transition when list items rearrange. Vue uses the FLIP technique internally, recording positions before and after a change, then animating the difference via `transform`. ```vue <TransitionGroup name="list" tag="ul"> <li v-for="item in items" :key="item.id"> {{ item.text }} </li> </TransitionGroup> <style> .list-enter-active, .list-leave-active { transition: all 0.3s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); } .list-move { transition: transform 0.3s ease; /* handles reorder animation */ } .list-leave-active { position: absolute; /* takes leaving item out of flow */ } </style> ``` The `.list-move` class is the key detail. Without it, items snap to new positions when one is removed. And `position: absolute` on `.list-leave-active` is needed because a leaving element still holds layout space while fading out. Taking it out of flow lets the remaining items slide into place. ### Transition modes By default `<Transition>` runs enter and leave at the same time. For component swaps that usually causes overlap. `mode="out-in"` fixes it. ```vue <!-- current component leaves fully, then the next one enters --> <Transition name="fade" mode="out-in"> <component :is="currentView" :key="currentView" /> </Transition> ``` Always add `:key` on the component inside `<Transition>`. Without it, Vue may reuse the same DOM node and skip the animation entirely. `mode="in-out"` does the opposite: new enters while old is still visible, then old leaves. Less common, but useful for layered overlay effects. ### JavaScript hooks CSS handles most cases. When you need a timeline, spring physics, or SVG morphing, use hooks and hand control to an animation library: ```vue <Transition @before-enter="onBeforeEnter" @enter="onEnter" @leave="onLeave" :css="false" > <div v-if="show">Content</div> </Transition> <script setup> import gsap from 'gsap' function onBeforeEnter(el) { gsap.set(el, { opacity: 0, y: -20 }) } function onEnter(el, done) { gsap.to(el, { opacity: 1, y: 0, duration: 0.4, onComplete: done }) } function onLeave(el, done) { gsap.to(el, { opacity: 0, y: 20, duration: 0.3, onComplete: done }) } </script> ``` Set `:css="false"` when using JS hooks exclusively. It tells Vue to skip class injection. If you skip this prop, Vue may call `done` too early and conflict with your library's timing. ### Common mistakes **Missing `:key` on dynamic components** ```vue <!-- Vue reuses the DOM node - transition does not fire --> <Transition name="fade" mode="out-in"> <component :is="currentView" /> </Transition> <!-- correct --> <Transition name="fade" mode="out-in"> <component :is="currentView" :key="currentView" /> </Transition> ``` **Forgetting `position: absolute` on `.list-leave-active`** Without it the leaving element holds space in the layout while fading. Other items do not move until Vue removes it, so reorder looks broken. This is the most common `<TransitionGroup>` issue I see in code reviews. **Not calling `done()` in JS hooks** The `@enter` and `@leave` hooks receive a `done` callback. If you never call it, Vue keeps the element in a transitioning state indefinitely. No further animations will fire on that element. **Wrapping multiple elements in `<Transition>`** `<Transition>` expects exactly one child. Two siblings inside it will cause a runtime warning. Wrap them in a single div or switch to `<TransitionGroup>`. ### Real-world usage - Modal dialogs: `<Transition name="modal" mode="out-in">` wrapping the overlay - Tab views: `mode="out-in"` on `<component :is="activeTab">` - Notification toasts: `<TransitionGroup>` with a slide-from-top name - Vue Router page transitions: wrap `<RouterView>` in `<Transition>` - Staggered list animations: `@enter` hook with `el.dataset.index` to calculate per-item delay ### Follow-up questions **Q:** What is the difference between `<Transition>` and `<TransitionGroup>`? **A:** `<Transition>` animates a single element on enter or leave. `<TransitionGroup>` animates a list and also handles move transitions when items reorder, using FLIP internally. **Q:** Why does `mode="out-in"` exist? **A:** Without it, enter and leave run simultaneously. For most view switches that causes two panels to overlap visually. `out-in` makes leave complete first, then enter starts. **Q:** What happens if you forget to call `done()` in a JS hook? **A:** Vue treats the transition as still running. The element stays stuck in its transitioning state and no subsequent transitions on it will trigger. **Q:** How does `<TransitionGroup>` animate items moving to new positions? **A:** It uses FLIP: records positions before and after the DOM change, applies an inverted transform so items appear to stay in place visually, then transitions to zero. The `.list-move` class controls that transition timing. **Q:** When would you use `:css="false"`? **A:** When JS hooks are doing all the animation work. It prevents Vue from adding transition classes that could interfere with your library's timing. ## Examples ### Basic fade toggle ```vue <template> <button @click="show = !show">Toggle message</button> <Transition name="fade"> <p v-if="show" class="notice">Saved successfully.</p> </Transition> </template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.25s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } </style> ``` Two CSS rules, no JavaScript. The notice fades in when `show` becomes `true` and fades out when it becomes `false`. ### View switcher with out-in mode ```vue <template> <nav> <button @click="view = 'HomeView'">Home</button> <button @click="view = 'ProfileView'">Profile</button> </nav> <Transition name="slide" mode="out-in"> <component :is="view" :key="view" /> </Transition> </template> <style> .slide-enter-active, .slide-leave-active { transition: transform 0.3s ease, opacity 0.3s ease; } .slide-enter-from { transform: translateX(20px); opacity: 0; } .slide-leave-to { transform: translateX(-20px); opacity: 0; } </style> ``` `mode="out-in"` makes the current view slide left and fade out before the next one slides in from the right. Remove `mode` and both animate at once, overlapping in the same space. ### Sortable list with move animation ```vue <template> <button @click="shuffle">Shuffle cards</button> <TransitionGroup name="cards" tag="ul" class="card-list"> <li v-for="card in cards" :key="card.id" class="card"> {{ card.label }} </li> </TransitionGroup> </template> <script setup> import { ref } from 'vue' const cards = ref([ { id: 1, label: 'Card A' }, { id: 2, label: 'Card B' }, { id: 3, label: 'Card C' }, ]) function shuffle() { cards.value = [...cards.value].sort(() => Math.random() - 0.5) } </script> <style> .card-list { position: relative; /* required for absolute-positioned leaving cards */ list-style: none; padding: 0; } .cards-move { transition: transform 0.4s ease; } .cards-enter-active, .cards-leave-active { transition: all 0.3s ease; } .cards-enter-from, .cards-leave-to { opacity: 0; transform: scale(0.8); } .cards-leave-active { position: absolute; } </style> ``` Each shuffle triggers FLIP on every card that moved. The `.cards-move` class handles smooth repositioning. The parent `position: relative` is required so that absolute-positioned leaving cards stay anchored visually during their exit animation.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.