When Vue renders a list with v-for, it needs a way to tell which DOM element corresponds to which item in the array. The :key attribute is that identifier. Without it (or with a bad key), Vue takes shortcuts that can cause real bugs.
What goes wrong without a proper key
Without a unique key, Vue reuses DOM elements by position. If you remove the second item from a list, Vue doesn't remove the second <li> — it updates the text of items 2, 3, 4... and removes the last one. This is efficient for simple text, but breaks when elements have their own state.
<script setup lang="ts">
import { ref } from 'vue'
const items = ref([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' }
])
function removeFirst() {
items.value.shift()
}
</script>
<template>
<!-- ❌ key=index: after removing Apple, the input that had Apple's
typed text now sits next to Banana — state is mismatched -->
<div v-for="(item, index) in items" :key="index">
<span>{{ item.name }}</span>
<input placeholder="Type something" />
</div>
<!-- ✅ key=item.id: Vue correctly removes Apple's entire DOM node,
Banana and Cherry keep their inputs and state -->
<div v-for="item in items" :key="item.id">
<span>{{ item.name }}</span>
<input placeholder="Type something" />
</div>
</template>Type something in each input, then remove the first item. With key=index, the inputs shuffle. With key=item.id, the correct DOM node is removed and everything else stays put.
The rules
- Always use
:keyonv-forelements. - Use a unique, stable identifier — an
idfrom your data, a database primary key, a slug. Something that stays the same across re-renders. - Never use the array index as key if items can be reordered, inserted, or removed. The index changes when the array changes, which defeats the purpose.
- Keys must be primitives — strings or numbers. Objects don't work.
<!-- ✅ Good: stable ID from the data -->
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
<!-- ✅ Good: stable unique string -->
<li v-for="tab in tabs" :key="tab.slug">{{ tab.label }}</li>
<!-- ❌ Bad: index shifts when array changes -->
<li v-for="(item, i) in items" :key="i">{{ item.name }}</li>When index is fine
If the list is static (never reordered, no additions or deletions) and items have no internal state (no inputs, no child components), then key=index won't cause bugs. But using a real ID is a safe habit that costs nothing.
See also: How does list rendering work with v-for? · What's the difference between v-if and v-show?
References
- Maintaining State with key - Vue.js docs
- v-for - Vue.js docs
- List Rendering - Vue.js docs