Vue transitions fail to animate on hide when the element is removed from the DOM before the leave animation completes or when CSS classes are not applied correctly during the unmounting phase. This usually stems from missing
v-if toggling logic, incorrect class naming in the
<transition> component, or asynchronous data updates that prevent the exit hook from triggering. By ensuring the boolean flag controls visibility and the CSS classes match the transition definitions, you can restore smooth show and hide animations.
Understanding the Transition Lifecycle
In Vue.js, the
<transition> component manages the entry and exit of elements wrapped within it. The most common mistake developers make is assuming that simply hiding an element with
display: none will trigger a transition. Vue requires the element to be physically present in the DOM while the leave animation plays out. If you use
v-show, the element remains in the DOM but becomes invisible immediately, which bypasses the leave transition entirely. You must use
v-if to remove the element from the DOM only after the animation has finished.
The lifecycle involves several hooks:
enter,
enter-active,
leave, and
leave-active. When an element is added, Vue adds the enter classes, applies the style, removes the active classes once done, and then removes the base classes. The reverse happens on removal. If the element disappears too quickly, the browser does not have time to render the intermediate states required for the animation.
Common Causes for One-Way Transitions
There are three primary reasons why a transition might work for showing an element but fail when hiding it:
- Using v-show instead of v-if: As mentioned,
v-show toggles CSS display properties directly without removing the node. This prevents the leave transition from ever starting.
- Incorrect Class Names: If the CSS classes defined in your stylesheet do not exactly match the names expected by the
<transition> component (e.g., using .fade-enter instead of the default .v-enter), the styles will not apply.
- Asynchronous Data Issues: If the variable controlling visibility is updated asynchronously or fails to update due to reactivity problems, the component may not re-render to trigger the exit hook.
Implementing the Correct Toggle Logic
To fix this issue, ensure you are using
v-if on the element you wish to animate. Here is a standard implementation pattern:
<template>
<div id="demo">
<button @click="show = !show">
Toggle
</button>
<transition name="fade" mode="out-in">
<p v-if="show">Hello</p>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
}
}
</script>
In this example, clicking the button toggles the
show data property. Because
v-if is used, Vue will mount the
<p> element when
show becomes true and unmount it when false. The
<transition> component listens for these mount/unmount events and applies the appropriate CSS classes.
If you need to switch between two different components rather than just showing/hiding one, consider adding the
mode="out-in" attribute. This ensures the leaving animation completes before the entering animation begins, preventing visual glitches where both elements overlap abruptly. For more details on managing component state, you might find
Understanding and Troubleshooting React Components useful, as the conceptual approach to state-driven UI changes shares similarities across frameworks.
CSS Configuration for Smooth Animations
The CSS classes must correspond to the
name prop passed to the
<transition> component. In the previous example, the name is
fade. Therefore, you need the following CSS rules:
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.fade-enter-to, .fade-leave {
opacity: 1;
}
Note the distinction between the base classes (
.fade-enter,
.fade-leave) and the active classes (
.fade-enter-active,
.fade-leave-active). The active classes define the duration and type of transition (e.g., opacity, transform). The base classes define the starting state, while the
-to variants define the ending state. If you omit the
-to classes in Vue 3, the browser defaults to the computed style of the element, which might cause flickering if not handled carefully.
Ensure that your CSS specificity is high enough to override any existing styles on the element. Using inline styles or highly specific selectors can sometimes prevent the transition classes from taking effect.
Debugging Reactive Updates
Sometimes the HTML structure is correct, but the transition still does not fire. This often indicates a reactivity issue. Verify that the variable controlling the
v-if is part of the component's
data() object. If you are updating the value from an external function or an API response, ensure you are using Vue's reactivity system correctly.
For instance, if you are fetching data via POST and updating the visibility based on the result, check that the assignment triggers a re-render. See
Troubleshooting Vue.js 2: Unable to Update Value After POST Function for insights into handling asynchronous data updates that might block UI transitions.
Also, inspect the browser console for warnings. Vue often emits warnings if you try to transition over non-element nodes or if there are conflicting keys in lists. Ensure that each item in a
v-for loop has a unique
:key attribute, as this helps Vue track node identity and apply transitions correctly to individual items.
Frequently Asked Questions
Why does my transition work on load but not on page refresh?
Transitions triggered by server-side rendering or initial page loads may behave differently because the DOM is already populated before Vue attaches. To fix this, you can use the
appear attribute on the
<transition> component to enable entry animations during the initial mount.
Can I use CSS variables inside transition classes?
Yes, you can use CSS custom properties (variables) within your transition classes. However, ensure that the variables are defined in a scope accessible to the transitioned element, and remember that changing a variable dynamically may require explicit re-triggering of the transition via JavaScript hooks if the change is subtle.
How do I handle transitions in nested components?
Transitions do not automatically propagate to child components. You must wrap the child component invocation in its own
<transition> tag if you want the child to animate. Alternatively, use
teleport to move the animated content outside the parent hierarchy if layout issues arise.
What is the difference between v-if and v-show for transitions?
v-if physically adds or removes the element from the DOM, allowing full entry and exit transitions.
v-show only toggles the CSS
display property, meaning the element is always in the DOM. Consequently,
v-show cannot trigger leave animations because the element never leaves the DOM tree.
How can I debug why a transition class isn't applying?
Open your browser's developer tools and inspect the element during the transition. Look for the presence of classes like
.fade-enter-active or
.fade-leave-active. If they are missing, check the console for Vue warnings regarding invalid nodes or mismatched keys. Also, verify that your CSS file is correctly linked and loaded in the network tab.