Introduction
Vue's <TransitionGroup> component animates elements entering, leaving, and moving within a list. Each child element must have a unique key attribute for Vue to track which element is which during transitions. Without unique keys, animations glitch, elements jump to wrong positions, or transitions do not fire at all:
[Vue warn]: <TransitionGroup> children must be keyed.Symptoms
- List items jump or teleport instead of smoothly transitioning
- Enter/leave animations do not fire for some items
- Multiple items animate as a single element
- Console warning about missing or duplicate keys
- Reordering the list produces chaotic animation instead of smooth movement
Common Causes
- Missing
keyattribute on TransitionGroup children - Using array index as key (
:key="index") which changes during reordering - Non-unique IDs in the data (e.g., items from different sources sharing IDs)
- Key generated from non-unique data fields
- Dynamic components in TransitionGroup with duplicate keys
Step-by-Step Fix
- 1.Use a truly unique identifier for each list item:
- 2.```html
- 3.<TransitionGroup name="list" tag="ul">
- 4.<li
- 5.v-for="item in items"
- 6.:key="item.id"
- 7.class="list-item"
- 8.>
- 9.{{ item.text }}
- 10.</li>
- 11.</TransitionGroup>
- 12.
` - 13.Never use array index as key for animated lists:
- 14.```html
- 15.<!-- BAD: index changes when items are reordered, breaking animations -->
- 16.<li v-for="(item, index) in items" :key="index">
<!-- GOOD: stable ID-based key --> <li v-for="item in items" :key="item.id"> ```
- 1.Generate unique keys for items without IDs:
- 2.```javascript
- 3.import { v4 as uuidv4 } from 'uuid';
function addItem(text) { items.value.push({ id: uuidv4(), text, createdAt: Date.now(), }); } ```
- 1.Define proper CSS transition classes for the animation:
- 2.```css
- 3..list-enter-active,
- 4..list-leave-active {
- 5.transition: all 0.3s ease;
- 6.}
- 7..list-enter-from,
- 8..list-leave-to {
- 9.opacity: 0;
- 10.transform: translateX(30px);
- 11.}
- 12..list-move {
- 13.transition: transform 0.3s ease;
- 14.}
- 15.
` - 16.The
.list-moveclass is essential for FLIP animations when items change position. - 17.Use TransitionGroup with dynamic components:
- 18.```html
- 19.<TransitionGroup name="fade" tag="div">
- 20.<component
- 21.v-for="widget in widgets"
- 22.:key="widget.id"
- 23.:is="widget.type"
- 24.:data="widget.data"
- 25./>
- 26.</TransitionGroup>
- 27.
`
Prevention
- Always use stable, unique IDs as keys for TransitionGroup children
- Ensure your data source generates unique identifiers for all list items
- Test list animations with add, remove, and reorder operations
- Use Vue DevTools to inspect the key values of TransitionGroup children
- Add the
eslint-plugin-vuerulevalid-v-forto catch missing keys - For FLIP animations, always include the
-moveCSS class - Keep transition durations consistent across enter, leave, and move states
- Test animations in both Chrome and Firefox as they handle CSS transitions differently