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:

bash
[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 key attribute 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. 1.Use a truly unique identifier for each list item:
  2. 2.```html
  3. 3.<TransitionGroup name="list" tag="ul">
  4. 4.<li
  5. 5.v-for="item in items"
  6. 6.:key="item.id"
  7. 7.class="list-item"
  8. 8.>
  9. 9.{{ item.text }}
  10. 10.</li>
  11. 11.</TransitionGroup>
  12. 12.`
  13. 13.Never use array index as key for animated lists:
  14. 14.```html
  15. 15.<!-- BAD: index changes when items are reordered, breaking animations -->
  16. 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. 1.Generate unique keys for items without IDs:
  2. 2.```javascript
  3. 3.import { v4 as uuidv4 } from 'uuid';

function addItem(text) { items.value.push({ id: uuidv4(), text, createdAt: Date.now(), }); } ```

  1. 1.Define proper CSS transition classes for the animation:
  2. 2.```css
  3. 3..list-enter-active,
  4. 4..list-leave-active {
  5. 5.transition: all 0.3s ease;
  6. 6.}
  7. 7..list-enter-from,
  8. 8..list-leave-to {
  9. 9.opacity: 0;
  10. 10.transform: translateX(30px);
  11. 11.}
  12. 12..list-move {
  13. 13.transition: transform 0.3s ease;
  14. 14.}
  15. 15.`
  16. 16.The .list-move class is essential for FLIP animations when items change position.
  17. 17.Use TransitionGroup with dynamic components:
  18. 18.```html
  19. 19.<TransitionGroup name="fade" tag="div">
  20. 20.<component
  21. 21.v-for="widget in widgets"
  22. 22.:key="widget.id"
  23. 23.:is="widget.type"
  24. 24.:data="widget.data"
  25. 25./>
  26. 26.</TransitionGroup>
  27. 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-vue rule valid-v-for to catch missing keys
  • For FLIP animations, always include the -move CSS class
  • Keep transition durations consistent across enter, leave, and move states
  • Test animations in both Chrome and Firefox as they handle CSS transitions differently