Back
Vue 3

Smooth Transitions and Animations in Vue 3

Master Vue 3's transition system. Learn to create CSS and JavaScript animations with Transition, TransitionGroup, and custom hooks.

Francisco ZapataWritten by Francisco Zapata
January 19, 202610 min read
Smooth Transitions and Animations in Vue 3

Animations bring user interfaces to life. Vue 3 offers a built-in transition system that makes it easy to animate elements as they enter, leave, or change in the DOM. With the <Transition> and <TransitionGroup> components, you can create polished visual experiences without relying on external libraries.

The Transition Component

The <Transition> component applies enter and leave animations to a single element or component. Vue automatically handles adding and removing CSS classes at the right time.

<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, world!</p>
  </Transition>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

Transition Classes

Vue applies 6 classes during the transition lifecycle:

Enter:
  • v-enter-from: Starting state before entering
  • v-enter-active: Active state during enter (define your transition here)
  • v-enter-to: Ending state of enter
Leave:
  • v-leave-from: Starting state before leaving
  • v-leave-active: Active state during leave
  • v-leave-to: Ending state of leave

When using name="fade", classes are prefixed: fade-enter-from, fade-enter-active, etc.

Advanced CSS Transitions

You can combine transform, opacity, and other properties for more elaborate effects:

<Transition name="slide-fade">
  <div v-if="show" class="box">Content</div>
</Transition>

<style>
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}
</style>

CSS Keyframe Animations

For more complex animations, use @keyframes:

<Transition name="bounce">
  <p v-if="show">Bounce!</p>
</Transition>

<style>
.bounce-enter-active {
  animation: bounce-in 0.5s;
}

.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}

@keyframes bounce-in {
  0% { transform: scale(0); }
  50% { transform: scale(1.25); }
  100% { transform: scale(1); }
}
</style>

Transition Modes

When toggling between two elements, both transitions happen simultaneously by default. Use mode to control the order:

<Transition name="fade" mode="out-in">
  <component :is="currentComponent" :key="currentTab" />
</Transition>
  • out-in: Current element leaves first, then new one enters (most common)
  • in-out: New element enters first, then current one leaves

JavaScript Hooks

For programmatic animations or integration with libraries like GSAP, use JavaScript hooks:

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  :css="false"
>
  <div v-if="show">Animated with JS</div>
</Transition>

<script setup>
function onEnter(el, done) {
  gsap.to(el, {
    opacity: 1,
    y: 0,
    duration: 0.5,
    onComplete: done
  })
}

function onLeave(el, done) {
  gsap.to(el, {
    opacity: 0,
    y: 20,
    duration: 0.3,
    onComplete: done
  })
}
</script>

The :css="false" prop tells Vue not to apply CSS classes, giving full control to JavaScript. It is important to call done() to signal that the animation has finished.

TransitionGroup: Animating Lists

<TransitionGroup> is for animating multiple elements, such as dynamic lists:

<script setup>
import { ref } from 'vue'

const items = ref([1, 2, 3, 4, 5])
let id = items.value.length

function addItem() {
  const index = Math.floor(Math.random() * items.value.length)
  items.value.splice(index, 0, ++id)
}

function removeItem(item) {
  items.value = items.value.filter(i => i !== item)
}
</script>

<template>
  <button @click="addItem">Add</button>

  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item" @click="removeItem(item)">
      {{ item }}
    </li>
  </TransitionGroup>
</template>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

/* Important: animate the movement of remaining elements */
.list-move {
  transition: transform 0.5s ease;
}

.list-leave-active {
  position: absolute;
}
</style>

Differences from Transition

  • Renders an actual container element (defined with tag)
  • Does not support mode (out-in / in-out)
  • Child elements MUST have a unique :key
  • Classes are applied to each individual child
  • Supports the v-move class for animating repositioning

Route Transitions with Vue Router

You can animate page transitions by combining <Transition> with <RouterView>:

<RouterView v-slot="{ Component, route }">
  <Transition :name="route.meta.transition || 'fade'" mode="out-in">
    <component :is="Component" :key="route.path" />
  </Transition>
</RouterView>

This allows defining transitions per route using meta fields:

const routes = [
  {
    path: '/home',
    component: Home,
    meta: { transition: 'slide-left' }
  },
  {
    path: '/about',
    component: About,
    meta: { transition: 'slide-right' }
  }
]

Appear Transitions

To animate elements when the page first loads:

<Transition appear name="fade">
  <h1>Welcome</h1>
</Transition>

Performance Tips

1. Use will-change

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
  will-change: opacity;
}

2. Prefer transform and opacity

These properties are the most efficient because they do not cause layout reflow:

/* Efficient */
.slide-enter-from { transform: translateX(100px); opacity: 0; }

/* Less efficient - causes reflow */
.slide-enter-from { margin-left: 100px; opacity: 0; }

3. Avoid animating too many elements

With <TransitionGroup> and large lists, consider pagination or virtualization to avoid animating hundreds of elements simultaneously.

4. Use v-show instead of v-if for frequent toggles

v-show keeps the element in the DOM and only toggles its visibility, which is more efficient for frequent toggles.

Conclusion

Vue 3's transition system is versatile and accessible. From simple fades to complex list animations, you have the tools you need without additional dependencies. Start with simple CSS transitions and evolve to JavaScript hooks when you need more control. The key is finding the balance between visual fluidity and performance.

Comments (0)

Leave a comment

Be the first to comment