Vue.js Transitions and Animations

Vue has a built-in system for animating elements when they are added to or removed from the page. The <Transition> and <TransitionGroup> components handle the timing, and you write the visual effect with CSS.

How Vue Transitions Work

When an element inside <Transition> appears or disappears, Vue temporarily adds special CSS classes at each stage of the animation. You write CSS rules for those classes to create the visual effect.

Diagram: Transition Class Stages

ENTERING (element appears):
  Stage 1: v-enter-from   → starting state (e.g., opacity: 0)
  Stage 2: v-enter-active → transition is running (e.g., transition: opacity 0.3s)
  Stage 3: v-enter-to     → ending state (e.g., opacity: 1)

LEAVING (element disappears):
  Stage 1: v-leave-from   → starting state (e.g., opacity: 1)
  Stage 2: v-leave-active → transition is running (e.g., transition: opacity 0.3s)
  Stage 3: v-leave-to     → ending state (e.g., opacity: 0)

Vue adds each class at the right moment and removes it when done.

Basic Fade Transition

<div id="app">
  <button @click="show = !show">Toggle Message</button>

  <Transition>
    <p v-if="show">Hello! I fade in and out.</p>
  </Transition>
</div>

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

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

<script>
  Vue.createApp({
    data() { return { show: true }; }
  }).mount("#app");
</script>

Diagram: Fade Effect Timeline

User clicks "Toggle" (show becomes false):

Time 0ms:    v-leave-from added  → opacity: 1 (normal)
             v-leave-active added → transition: opacity 0.4s
Time 0ms+:   v-leave-to added    → opacity: 0 (fading out)
Time 400ms:  transition complete → element removed from DOM

User clicks again (show becomes true):

Time 0ms:    v-enter-from added  → opacity: 0 (invisible)
             v-enter-active added → transition: opacity 0.4s
Time 0ms+:   v-enter-to added    → opacity: 1 (fading in)
Time 400ms:  transition complete → classes removed

Named Transitions

Give a transition a name using the name prop. Vue uses that name instead of v as the class prefix. This lets you define multiple different transitions on the same page.

<Transition name="slide">
  <p v-if="show">I slide in from the left.</p>
</Transition>

<style>
/* Classes are now: slide-enter-from, slide-enter-active, etc. */
.slide-enter-active,
.slide-leave-active {
  transition: transform 0.4s ease, opacity 0.4s ease;
}

.slide-enter-from {
  transform: translateX(-40px);
  opacity: 0;
}

.slide-leave-to {
  transform: translateX(40px);
  opacity: 0;
}
</style>

Diagram: Named Transition Class Prefix

<Transition> (no name):
  Classes: v-enter-from, v-enter-active, v-enter-to
           v-leave-from, v-leave-active, v-leave-to

<Transition name="slide">:
  Classes: slide-enter-from, slide-enter-active, slide-enter-to
           slide-leave-from, slide-leave-active, slide-leave-to

<Transition name="bounce">:
  Classes: bounce-enter-from, bounce-enter-active, bounce-enter-to
           bounce-leave-from, bounce-leave-active, bounce-leave-to

CSS Animation — Using @keyframes

You can use CSS @keyframes animations instead of transitions. Apply the animation in the -active class.

<Transition name="pop">
  <div v-if="show" class="badge">New!</div>
</Transition>

<style>
.pop-enter-active {
  animation: pop-in 0.4s ease;
}
.pop-leave-active {
  animation: pop-in 0.3s ease reverse;
}

@keyframes pop-in {
  0%   { transform: scale(0); opacity: 0; }
  70%  { transform: scale(1.2); }
  100% { transform: scale(1); opacity: 1; }
}
</style>

Transition Modes

When you switch between two elements (using v-if / v-else), both the entering and leaving transitions run at the same time by default. The mode prop controls the order.

<!-- Both transitions run at the same time (default) -->
<Transition>
  <button v-if="editing">Save</button>
  <button v-else>Edit</button>
</Transition>

<!-- Old element leaves first, then new one enters -->
<Transition name="fade" mode="out-in">
  <button v-if="editing">Save</button>
  <button v-else>Edit</button>
</Transition>

<!-- New element enters first, then old one leaves -->
<Transition name="fade" mode="in-out">
  ...
</Transition>

Diagram: out-in Mode

Without mode (simultaneous):
  "Edit" fades out ──────────────▶ done
  "Save" fades in  ──────────────▶ done
  (both happen at the same time — can look jumpy)

mode="out-in" (sequential):
  "Edit" fades out ──────────────▶ done
                                        │
                                        ▼
                               "Save" fades in ──────▶ done
  (clean, one at a time)

TransitionGroup — Animating Lists

<TransitionGroup> animates multiple elements — like a list — when items are added, removed, or reordered. Unlike <Transition>, it renders a real DOM element (default: <span>) or one you specify with the tag prop.

<div id="app">
  <button @click="addItem">Add Item</button>

  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.text }}
    </li>
  </TransitionGroup>
</div>

<style>
.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 movement when items shift positions */
.list-move {
  transition: transform 0.4s ease;
}
</style>

Diagram: TransitionGroup Item Lifecycle

List: [Item A, Item B, Item C]

Add Item D:
  Item D: enter-from (invisible, shifted left)
  → enter-active (transition runs)
  → enter-to (fully visible)
  [Item A, Item B, Item C, Item D]

Remove Item B:
  Item B: leave-from (visible)
  → leave-active (transition runs)
  → leave-to (invisible, shifted right)
  Item C moves up: .list-move handles smooth repositioning
  [Item A, Item C, Item D]

Appear — Animate on First Load

By default, the transition does not run when the component first loads. Add the appear prop to animate the element on its initial render.

<Transition name="fade" appear>
  <div class="hero-banner">
    Welcome to our site!
  </div>
</Transition>

JavaScript Hooks for Complex Animations

For animations that CSS cannot easily handle — physics, dynamic values, third-party libraries — Vue provides JavaScript lifecycle hooks on the <Transition> component.

<Transition
  @before-enter="beforeEnter"
  @enter="enter"
  @leave="leave"
>
  <div v-if="show">Animated element</div>
</Transition>

<script>
  methods: {
    beforeEnter(el) {
      el.style.opacity = 0;
    },
    enter(el, done) {
      // Use a library like GSAP here
      el.style.transition = "opacity 0.5s";
      el.style.opacity = 1;
      setTimeout(done, 500);
    },
    leave(el, done) {
      el.style.transition = "opacity 0.5s";
      el.style.opacity = 0;
      setTimeout(done, 500);
    }
  }
</script>

Summary

The <Transition> component wraps a single element and applies CSS classes at six stages of its enter and leave animation. Name the transition to use a custom class prefix and define multiple distinct animations. Use mode="out-in" for clean sequential switching between two elements. <TransitionGroup> handles animated lists, including smooth repositioning with the -move class. Add appear for on-load animations, and use JavaScript hooks for advanced animation scenarios that go beyond CSS.

Leave a Comment

Your email address will not be published. Required fields are marked *