iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Transitions

`` and `` wrap a single (or list) element and apply CSS classes during enter/leave phases. Vue inserts class names at the right moments; you write the keyframes. The component layers cleanly on Tailwind, custom CSS, or third-party motion libraries.

Single, list, JS hooks, and route transitions

EXAMPLE
<!-- 1) Single-element fade -->
<script setup>
import { ref } from 'vue';
const open = ref(false);
</script>

<template>
  <button @click='open = !open'>Toggle</button>
  <Transition name='fade'>
    <p v-if='open'>Hello</p>
  </Transition>
</template>

<style>
.fade-enter-active, .fade-leave-active { transition: opacity .15s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>

<!-- 2) Slide + fade together -->
<style>
.slide-enter-active { transition: transform .25s cubic-bezier(.2,.7,.2,1), opacity .2s; }
.slide-leave-active { transition: transform .15s ease-in, opacity .15s; }
.slide-enter-from   { opacity: 0; transform: translateY(8px); }
.slide-leave-to     { opacity: 0; transform: translateY(-8px); }
</style>

<!-- 3) Transition between two elements (use a key on each) -->
<Transition name='slide' mode='out-in'>
  <p :key='step'>{{ step === 1 ? 'Step 1' : 'Step 2' }}</p>
</Transition>

<!-- 4) TransitionGroup — animate ADDED / REMOVED / MOVED list items -->
<script setup>
import { ref } from 'vue';
const items = ref([1, 2, 3]);
function shuffle() {
  items.value = items.value.slice().sort(() => Math.random() - 0.5);
}
function add() { items.value.push(items.value.length + 1); }
</script>

<template>
  <button @click='shuffle'>Shuffle</button>
  <button @click='add'>Add</button>
  <TransitionGroup name='list' tag='ul'>
    <li v-for='n in items' :key='n'>{{ n }}</li>
  </TransitionGroup>
</template>

<style>
.list-move,
.list-enter-active,
.list-leave-active { transition: all .35s cubic-bezier(.2,.7,.2,1); }
.list-enter-from,
.list-leave-to   { opacity: 0; transform: translateY(6px); }
.list-leave-active { position: absolute; }   /* prevents layout jump on remove */
</style>

<!-- 5) JS hooks — run code at lifecycle points (mix with GSAP / anime.js) -->
<Transition
  @before-enter='(el) => { el.style.opacity = 0; }'
  @enter='(el, done) => { el.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 200 }).onfinish = done; }'
  @leave='(el, done) => { el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 150 }).onfinish = done; }'
>
  <div v-if='show'>Custom-animated</div>
</Transition>

<!-- 6) Route-level transitions (Vue Router 4) -->
<router-view v-slot='{ Component }'>
  <Transition name='fade' mode='out-in'>
    <component :is='Component' />
  </Transition>
</router-view>

<!-- 7) Respect prefers-reduced-motion -->
<style>
@media (prefers-reduced-motion: reduce) {
  .fade-enter-active, .fade-leave-active,
  .slide-enter-active, .slide-leave-active,
  .list-move, .list-enter-active, .list-leave-active {
    transition: none !important;
  }
}
</style>

<!-- 8) mode='out-in' vs 'in-out'
     'out-in' (default): old leaves, then new enters. Good for content swaps.
     'in-out':           new enters on top of old. Good for fade through hero images.
-->

Why it matters

`` solves the awful "items jump when one is added" problem with its FLIP animation, but you must set `position: absolute` on `.leave-active` items, otherwise the leaving element keeps occupying space and the rest of the list refuses to settle. Once that detail is right, list mutations look professional with three lines of CSS.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<Transition name="fade">
    <div v-if="show">Hi</div>
</Transition>
Try it Yourself »

Discussion

Loading…