Chapter 51: Vue Transition Component
The <Transition> component
This is Vue’s built-in, zero-dependency wrapper that makes almost any enter/leave or appear/disappear animation feel smooth and professional — with almost no JavaScript.
You will use <Transition> (and its bigger brother <TransitionGroup>) in almost every real Vue application — modals, alerts, dropdowns, sidebars, loading spinners, tab content, tooltips, notifications, list items, router-view transitions… the list is very long.
So let’s understand it properly — step by step, with clear mental model, real examples, and the exact CSS classes you will write 100+ times in your career.
1. Mental Model – What <Transition> Actually Does
<Transition> is not an animation library — it is a state machine that watches when its single direct child appears or disappears (usually controlled by v-if / v-show) and automatically adds/removes specific CSS classes at the right moments.
You write CSS transitions (or animations) that target those class names — Vue handles the timing and class toggling.
Important rules (memorize these)
- <Transition> can have only one direct child (element or component)
- The child must be conditionally rendered with v-if / v-show / <router-view> / dynamic :is
- Vue adds/removes 6 class names during the lifecycle (enter & leave phases)
- You usually control only 4 of them (the -from and -active ones)
2. The 6 Class Names Vue Adds (The Core Magic)
Let’s say you wrap with <Transition name=”fade”>
| Phase | Classes added / active | Typical CSS you write | Duration |
|---|---|---|---|
| Before enter | fade-enter-from | initial state (opacity: 0, transform: scale(0.9)) | — |
| Enter animation starts | fade-enter-active | transition: opacity 0.4s ease; | active |
| Enter finishes | removes both, no more classes | final state (opacity: 1) | — |
| Before leave | fade-leave-from (usually same as final state) | — | — |
| Leave animation starts | fade-leave-active | transition: opacity 0.4s ease; | active |
| Leave finishes | fade-leave-to | end state (opacity: 0, transform: scale(0.95)) | — |
You almost never write -enter-to or -leave-from — Vue assumes they are the same as the opposite state.
3. Real Example 1 – Classic Modal Fade + Scale
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<template> <div> <button @click="showModal = !showModal"> {{ showModal ? 'Close' : 'Open' }} Modal </button> <Transition name="modal"> <div v-if="showModal" class="modal-overlay" @click.self="showModal = false"> <div class="modal-content"> <h2>Confirmation</h2> <p>Are you sure you want to proceed?</p> <button @click="showModal = false">Cancel</button> <button class="confirm">Yes</button> </div> </div> </Transition> </div> </template> <script setup> import { ref } from 'vue' const showModal = ref(false) </script> <style scoped> .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; } .modal-content { background: white; padding: 2rem; border-radius: 12px; max-width: 500px; box-shadow: 0 20px 50px rgba(0,0,0,0.3); } /* ──────────────────────────────────────── The actual transition classes ───────────────────────────────────────── */ .modal-enter-active, .modal-leave-active { transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); } .modal-enter-from, .modal-leave-to { opacity: 0; transform: scale(0.85) translateY(40px); } </style> |
Result: Modal fades in + scales up smoothly when appearing Fades out + scales down when disappearing No JavaScript animation code — pure CSS
4. Real Example 2 – Slide from Side (Sidebar / Drawer)
|
0 1 2 3 4 5 6 7 8 9 10 |
<Transition name="slide"> <aside v-if="showSidebar" class="sidebar"> Sidebar content </aside> </Transition> |
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.slide-enter-active, .slide-leave-active { transition: transform 0.35s ease-out; } .slide-enter-from, .slide-leave-to { transform: translateX(-100%); } |
→ Sidebar slides in from left, slides out to left
5. Real Example 3 – List Animation with <TransitionGroup>
(You asked for v-for animations in previous lesson — this is the companion)
|
0 1 2 3 4 5 6 7 8 9 10 |
<TransitionGroup name="list" tag="ul"> <li v-for="item in items" :key="item.id"> {{ item.name }} </li> </TransitionGroup> |
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.list-enter-active, .list-leave-active, .list-move { transition: all 0.4s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateX(-40px); } .list-leave-active { position: absolute; width: 100%; } |
→ Items fade + slide when added/removed → Positions animate smoothly when order changes (.move class)
6. Quick Reference Table – Most Used Transition Names
| Name you set | Effect you get | CSS classes you write (4 main) | Typical duration |
|---|---|---|---|
| fade | Simple opacity fade | opacity 0 → 1 | 0.3–0.5s |
| slide | Slide from left/right | transform: translateX(-100%) | 0.35s |
| scale | Zoom in/out | transform: scale(0.9) | 0.4s |
| bounce | Fun overshoot bounce | transform: scale(0.8) → scale(1.05) → scale(1) | 0.5–0.7s |
| flip | 3D card flip | transform: rotateY(90deg) | 0.6s |
| list | For <TransitionGroup> – move + enter/leave | transform + opacity | 0.4s |
7. JavaScript Hooks (When CSS Alone Is Not Enough)
You can hook into every phase and use GSAP, Anime.js, Three.js, etc.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<Transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter" @enter-cancelled="enterCancelled" @before-leave="beforeLeave" @leave="leave" @after-leave="afterLeave" @leave-cancelled="leaveCancelled" > <div v-if="show">Content</div> </Transition> <script setup> import gsap from 'gsap' function beforeEnter(el) { el.style.opacity = '0' el.style.transform = 'scale(0.8)' } function enter(el, done) { gsap.to(el, { opacity: 1, scale: 1, duration: 0.6, ease: 'back.out(1.7)', onComplete: done }) } function afterEnter(el) { console.log('Animation finished') } </script> |
Pro Tips from Real Projects (Hyderabad 2026)
-
Always test mobile — animations feel very different on 60Hz vs 120Hz
-
Use cubic-bezier curves — cubic-bezier(0.34, 1.56, 0.64, 1) feels very natural
-
Respect prefers-reduced-motion (accessibility)
CSS012345678910@media (prefers-reduced-motion: reduce) {.fade-enter-active, .fade-leave-active {transition: none !important;}} -
Combine <Transition> + <KeepAlive> for animated cached tabs
-
For lists → always use <TransitionGroup> + :key — otherwise animations break
-
Keep durations 0.25s–0.6s — longer feels sluggish
Your mini practice task:
- Build a modal with <Transition name=”modal”> + scale + fade
- Add a list with <TransitionGroup name=”list”> + slide + move
- Create a tabbed interface with <component :is> + <Transition> + <KeepAlive>
Any part confusing? Want full examples for:
- Modal with slide-up + backdrop fade?
- Animated router-view transitions?
- GSAP + Vue Transition hooks?
- Animate on data change (not just v-if)?
Just tell me — we’ll animate the next beautiful component together 🚀
