skip to content

In Vue 3, when `<Transition>` swaps one view for another, what do `mode="out-in"` and `mode="in-out"` change, and what happens with no mode?

level: middleimportance: should knowfreq 52%

answer

  1. two elements in the DOM at once
  2. leave first, then insert
  3. enter first, then leave
  4. v-else branches or a changed key

basics

~20 s

With no mode, Vue 3's Transition runs the old element's leave and the new element's enter together. Mode out-in finishes the leave before inserting the new element; in-out enters the new element first and only then starts the old one's leave.

solid answer

~40 s

A swap happens when the single child changes identity: `v-if`/`v-else` branches, a different component, or the same element with a new `key`. By default `<Transition>` runs leave and enter simultaneously, so both elements sit in the DOM for the duration and the layout jumps unless you stack them with absolute positioning. `mode="out-in"` renders an empty placeholder while the old element leaves and inserts the new one only after that leave finishes — what most view swaps want. `mode="in-out"` inserts and animates the new element first and delays the old one's leave until the enter completes; it is rarely used. Any other value triggers a dev warning, `invalid <transition> mode`.

code

vue · 27 lines
vue
<script setup lang="ts">
import { ref } from 'vue'

const tab = ref<'profile' | 'billing'>('profile')
</script>

<template>
  <nav>
    <button @click="tab = 'profile'">Profile</button>
    <button @click="tab = 'billing'">Billing</button>
  </nav>
  <Transition name="fade" mode="out-in">
    <section v-if="tab === 'profile'">Profile settings</section>
    <section v-else>Billing settings</section>
  </Transition>
</template>

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

go deeper

for a junior

Know the three orderings by name and that out-in is the usual choice when swapping two views inside one Transition.

for a middle

Explain what triggers a swap — a branch change or a new key — and why the default mode causes a layout jump while out-in shows a placeholder.

for a senior

Weigh the extra latency of out-in against overlap positioning, and diagnose a swap that never animates because the element identity never changed.

for a principal

Set a house rule for view transitions: which mode and durations a design system uses, so perceived speed stays consistent across teams.

## What counts as a swap `<Transition>` wraps a **single** child. A swap is the moment that child is replaced by a different one, so that one element leaves while another enters. In Vue 3 that happens when: - a `v-if` / `v-else-if` / `v-else` chain switches branch — the template compiler gives each branch its own key, so even two `<section>` branches count as different elements; - the child is a different component than before; - the child keeps its tag but its **`key` changes**, for example `<span :key="count">{{ count }}</span>`. The last case surprises people. Without the `key`, a changing `count` only patches the span's text node; nothing is inserted or removed, so `<Transition>` has nothing to animate. With `:key="count"`, every value is a new identity, the old span is unmounted and a new one mounted, and the pair animates as a swap. ## The default: both at once With no `mode` prop (or `mode="default"`), Vue starts the old element's leave and the new element's enter **in the same update**. For the length of the animation both nodes are in the DOM. In normal flow layout they stack one after the other, so the page jumps: the new view appears below the old one, then snaps up when the old one is removed. The Vue guide's remedy for the default mode is to position the two elements absolutely (or place them in the same grid cell) so they overlap and crossfade. ## `mode="out-in"` 1. The key changes and the old element starts its leave classes. 2. Instead of the new child, `<Transition>` renders an empty placeholder, so nothing new is inserted yet. 3. When the old element's leave finishes and it is removed, Vue re-renders the `<Transition>`. 4. The new element is inserted and runs its enter classes. The result is a clean sequence with only one element present at a time. It is the usual choice for swapping whole views — tabs, wizard steps, an edit/save button pair. ## `mode="in-out"` 1. The new element is inserted and runs its enter immediately. 2. The old element stays in place, its leave postponed. 3. When the new element's enter finishes, the old element's leave starts. Both elements are visible for a while, so `in-out` still needs positioning care. It fits effects where the incoming layer slides over the outgoing one before the outgoing one fades. ## Side by side | | No mode | `out-in` | `in-out` | |---|---|---|---| | Order | leave and enter together | leave, then enter | enter, then leave | | Elements in DOM during swap | two | one (a placeholder between) | two | | Total time | max(leave, enter) | leave + enter | enter + leave | | Layout care | needs overlap positioning | none | needs overlap positioning | | Typical use | crossfades, counters | view swaps, tabs | overlay-style reveals | ## Choosing, and the traps - **`out-in` is slower.** It serialises two animations, so a 200 ms leave plus a 200 ms enter becomes 400 ms of transition, with a gap in between where the slot is empty and surrounding content may collapse for a moment. - **Rapid toggling.** If the key changes again while an `out-in` leave is running, Vue keeps showing the placeholder until that leave completes and then renders the latest child, so intermediate states are skipped rather than queued. That is usually what you want for tabs: a user clicking through three tabs quickly sees one fade-out and a fade-in of the last tab, not three animations played back in sequence. - **Only valid values.** `mode` accepts `in-out`, `out-in` or `default`; any other string logs `invalid <transition> mode` in development. - **One child at a time.** A mode orders two single children. Rendering two elements side by side inside `<Transition>` is not a swap at all — Vue warns that `<transition>` can only be used on a single element or component. The usual interview answer is: use `out-in` for view swaps, the default with overlapping positioning when you want a true crossfade, and remember that without a changed identity — a branch or a `key` — no swap happens at all.

  • Why does `<Transition>` around `<span>{{ count }}</span>` not animate when `count` changes, and how does `:key` fix it?
    The span stays the same element; Vue only patches its text, so nothing is inserted or removed and `<Transition>` has nothing to animate. Binding `:key="count"` gives each value its own identity: Vue unmounts the old span and mounts a new one, and `<Transition>` animates the pair as a swap — usually with `mode="out-in"` or with the two spans overlapped by positioning.
  • Do two same-tag `v-if`/`v-else` branches inside `<Transition>` need their own `key` in Vue 3?
    No. The Vue 3 template compiler injects a distinct key into each `v-if`/`v-else-if`/`v-else` branch, so two `<section>` branches are already different vnodes and swap with a transition. Vue 2 reused same-tag branches and needed explicit keys, which is why older code still carries them.
  • What does `out-in` cost the user, and when would you avoid it?
    It serialises the animations, so the swap takes leave plus enter, and the slot is empty in between, which can briefly collapse surrounding layout. For fast, frequent swaps such as a counter, a simultaneous crossfade with both elements overlapped in one grid cell often feels quicker; `out-in` suits whole-view swaps where overlap would look broken.

Out-in is a single-lane bridge: the car leaving must be fully across before the arriving car drives on. The default is a two-lane bridge where both cars are on it at once, which is why you must make room for two.

saying these in an interview costs you the question

  • Without a mode, Vue waits for the old element to leave before inserting the new one.
  • in-out removes the old element first and then enters the new one.
  • Changing the text inside one element triggers the enter and leave classes.
  • Same-tag v-if and v-else branches always need explicit keys in Vue 3.
  • out-in makes a swap faster because only one element animates at a time.