Meituan-Dianping/mpvue · warning
<transition-group> children must be keyed: <${name}>
Error message
<transition-group> children must be keyed: <${name}> What it means
<transition-group> implements FLIP-based move animations that require tracking each child by its `key`. During render it builds a children map keyed by `c.key`; if a child has no key it cannot be tracked, so in development Vue warns that transition-group children must be keyed, printing the child's component or tag name.
Source
Thrown at src/platforms/web/runtime/components/transition-group.js:54
render (h: Function) {
const tag: string = this.tag || this.$vnode.data.tag || 'span'
const map: Object = Object.create(null)
const prevChildren: Array<VNode> = this.prevChildren = this.children
const rawChildren: Array<VNode> = this.$slots.default || []
const children: Array<VNode> = this.children = []
const transitionData: Object = extractTransitionData(this)
for (let i = 0; i < rawChildren.length; i++) {
const c: VNode = rawChildren[i]
if (c.tag) {
if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
children.push(c)
map[c.key] = c
;(c.data || (c.data = {})).transition = transitionData
} else if (process.env.NODE_ENV !== 'production') {
const opts: ?VNodeComponentOptions = c.componentOptions
const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag
warn(`<transition-group> children must be keyed: <${name}>`)
}
}
}
if (prevChildren) {
const kept: Array<VNode> = []
const removed: Array<VNode> = []
for (let i = 0; i < prevChildren.length; i++) {
const c: VNode = prevChildren[i]
c.data.transition = transitionData
c.data.pos = c.elm.getBoundingClientRect()
if (map[c.key]) {
kept.push(c)
} else {
removed.push(c)
}
}
this.kept = h(tag, null, kept)View on GitHub (pinned to 6c5d78ee04)
Solutions
- Add a unique `:key` to every child rendered inside <transition-group>, e.g. `:key="item.id"` on the v-for element.
- Use a stable unique identifier (id from data), not the array index, so move/remove animations track correctly.
- Key any hardcoded/conditional children too (e.g. `<span key="label">`).
- If the list is truly static and needs no animation, use a plain container instead of transition-group.
Example fix
// before
<transition-group name="list">
<li v-for="item in items">{{ item.name }}</li>
</transition-group>
// after
<transition-group name="list">
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</transition-group> Defensive patterns
Strategy: validation
Validate before calling
// before rendering, verify every list item has a key source
items.forEach((item, i) => {
if (item.id == null) console.warn('transition-group child at index ' + i + ' has no unique id for :key')
}) Type guard
const hasKey = c => c && c.key != null
if (!children.every(hasKey)) console.warn('all transition-group children need :key') Prevention
- Always pair v-for inside transition-group with :key.
- Use stable unique ids, never the array index.
- Key hardcoded and v-if/v-else children inside transition-group too.
- Enable dev-mode linting (eslint-plugin-vue 'require-v-for-key') to catch this at lint time.
When it happens
Trigger: Rendering a list inside <transition-group> with v-for but no `:key` binding; rendering static, unkeyed elements as direct children of transition-group; v-for over objects where the key expression is undefined for some items.
Common situations: Forgetting `:key="item.id"` when adding transition-group to an existing v-for; keys bound to the loop index and then removed during refactors; conditional children (v-if) without keys inside a transition-group.
Related errors
- <template> cannot be keyed. Place the key on real elements i
- Avoid using non-primitive value as key, use string/number va
- Invalid v-for expression: ${exp}
- `key` does not work on <slot> because slots are abstract out
- Avoid using non-primitive value as key, use string/number va
AI-assisted analysis of Meituan-Dianping/mpvue@6c5d78ee04 (2026-09-02).
Data as JSON: /api/errors/6cc34d7b6ae14161.
Report an issue: GitHub.