React
Transitions in Vue 3: CSS animation without a library
Transition and TransitionGroup add and remove CSS classes as a node enters, leaves or moves: six lines of CSS cover a modal or a list.
Key points
- Transition animates nothing by itself: it applies classes and leaves the duration to CSS.
- Enter and leave have three phases: from, active, to. Duration belongs to active only.
- TransitionGroup needs stable :key values, or the animated reorder never starts.
In Vue 3 you animate a node without installing anything: <Transition> adds and removes CSS classes as the element enters or leaves, and the browser does the rest. The component holds no keyframes.
The classes Vue adds
import { ref } from "vue";
const open = ref(false);
Toggle
Saved.
.fade-enter-active,
.fade-leave-active {
transition: opacity 180ms ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
With name="fade" the classes become fade-enter-from, fade-enter-active and fade-enter-to, plus the three equivalents for leaving. Duration belongs on the active class only: that one stays applied for the whole transition.
Lists: TransitionGroup
import { ref } from "vue";
const items = ref([
{ id: 1, name: "Ada" },
{ id: 2, name: "Linus" },
]);
{{ item.name }}
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 220ms ease;
}
.list-leave-active {
position: absolute;
}
The list-move class animates the reorder: Vue applies it to elements that change position. Without a stable :key the group cannot match nodes and the movement never starts.
When CSS is enough and when it is not
| Case | Winner | Why |
|---|---|---|
| A modal appearing | Transition | Two classes and one opacity |
| Reordering a list | TransitionGroup | The move class handles the shift |
| A timeline with several steps | A library or GSAP | CSS does not orchestrate stateful sequences |
| Animation across a route change | View Transitions | The browser keeps the page state |
Common mistakes
- Putting duration on
enter-from: it is removed on the next frame and the animation jumps. - Animating
height: auto: the browser cannot interpolate an automatic value, so use an explicit measure orgrid-template-rows. - Forgetting
appear: the first render has no transition and the modal pops in. - Using
TransitionGroupwithouttag: the default wrapper is aspanand breaks the list layout.
The limitation to plan for
CSS transitions do not honour the system preference for reduced motion on their own. Wrap the block in @media (prefers-reduced-motion: reduce) and zero the duration: whoever asked for less motion gets an instant change and keeps the content.
Where to go next
In the catalogue: vue3-gsap-animations, vue-bits and shadcn-vue; animated sections are in /blocks. The guides: animations with Tailwind and View Transitions. The comparison: transitions in React.
Frequently asked questions
Do I need a library to animate in Vue 3?
No. Transition and TransitionGroup with CSS cover entering, leaving and reordering. A library helps for complex timelines or stateful animation.
Why does the animation never start?
The selector usually misses the transition name. With name set to fade the classes are fade-enter-active and fade-leave-active.
Does Transition work with v-if?
Yes, that is the main case. It also works with v-show and dynamic components, not with a plain text change.
How do I honour prefers-reduced-motion?
Wrap the CSS block in a media query that zeroes the duration: whoever asked for less motion gets an instant change.
Why does the layout jump when the animation ends?
You are animating an element that takes space in flow. Animate transform and opacity, or remove the node from flow while it leaves.