Transition Modes and Dynamic Transitions

+15 Mana ✨

Control how entering and leaving elements coordinate, and dynamically change transitions.

Transition Modes

By default, enter and leave happen simultaneously. Use mode to change this:

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

const currentView = ref('A')
</script>

<template>
  <button @click="currentView = currentView === 'A' ? 'B' : 'A'">
    Toggle View
  </button>
  
  <!-- Default: both animate simultaneously -->
  <Transition name="fade">
    <component :is="currentView === 'A' ? ViewA : ViewB" :key="currentView" />
  </Transition>
  
  <!-- out-in: leave first, then enter -->
  <Transition name="fade" mode="out-in">
    <component :is="currentView === 'A' ? ViewA : ViewB" :key="currentView" />
  </Transition>
  
  <!-- in-out: enter first, then leave -->
  <Transition name="fade" mode="in-out">
    <component :is="currentView === 'A' ? ViewA : ViewB" :key="currentView" />
  </Transition>
</template>

Mode Comparison

ModeBehaviorBest For
(default)SimultaneousCross-fades
out-inLeave → EnterTab switching
in-outEnter → LeaveOverlapping reveals

Dynamic Transitions

Change the transition based on state:

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

const direction = ref<'left' | 'right'>('right')
const currentStep = ref(1)

const transitionName = computed(() => 
  direction.value === 'right' ? 'slide-right' : 'slide-left'
)

function next() {
  direction.value = 'right'
  currentStep.value++
}

function prev() {
  direction.value = 'left'
  currentStep.value--
}
</script>

<template>
  <div class="wizard">
    <button @click="prev" :disabled="currentStep <= 1">Previous</button>
    <button @click="next" :disabled="currentStep >= 3">Next</button>
    
    <Transition :name="transitionName" mode="out-in">
      <Step1 v-if="currentStep === 1" key="1" />
      <Step2 v-else-if="currentStep === 2" key="2" />
      <Step3 v-else key="3" />
    </Transition>
  </div>
</template>

<style>
.slide-right-enter-active,
.slide-right-leave-active,
.slide-left-enter-active,
.slide-left-leave-active {
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.slide-right-enter-from {
  transform: translateX(100%);
  opacity: 0;
}
.slide-right-leave-to {
  transform: translateX(-100%);
  opacity: 0;
}

.slide-left-enter-from {
  transform: translateX(-100%);
  opacity: 0;
}
.slide-left-leave-to {
  transform: translateX(100%);
  opacity: 0;
}
</style>

Reusable Transition Components

vue
<!-- FadeTransition.vue -->
<template>
  <Transition
    name="fade"
    :mode="mode"
    :appear="appear"
    @after-enter="$emit('after-enter')"
    @after-leave="$emit('after-leave')"
  >
    <slot />
  </Transition>
</template>

<script setup lang="ts">
defineProps<{
  mode?: 'in-out' | 'out-in'
  appear?: boolean
}>()

defineEmits<{
  'after-enter': []
  'after-leave': []
}>()
</script>

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

Usage:

vue
<FadeTransition mode="out-in" appear>
  <div v-if="show">Content</div>
</FadeTransition>
✓ Completed