You already know that v-if toggles elements and v-for repeats them. This lesson goes deeper: when v-show beats v-if, how v-else-if chains work, how to loop over objects and ranges, why the key attribute matters for correctness, and how to filter and sort lists without mutating your data. By the end you will render any list correctly and efficiently.
Both hide content, but they work differently:
| | v-if | v-show |
| --- | --- | --- |
| Mechanism | Element is created or destroyed | Element stays; toggles display: none |
| Initial cost | Low if false (nothing rendered) | Always rendered |
| Toggle cost | High (mount and unmount) | Very low |
| Works on <template> | Yes | No |
| Pairs with v-else | Yes | No |
Use v-show for elements that toggle frequently (tabs, dropdowns, tooltips). Use v-if when the condition rarely changes or when the hidden branch is expensive to create, such as a heavy component or one that fetches data on mount.
<template>
<p v-if="status === 'loading'">Loading...</p>
<p v-else-if="status === 'error'">Something went wrong.</p>
<div v-else>Content is ready.</div>
<div v-show="isMenuOpen" class="menu">Menu items</div>
</template>v-else-if and v-else must immediately follow the previous branch element. To conditionally render several sibling elements at once, wrap them in <template v-if="...">, which renders no extra DOM node.
v-for accepts an optional index for arrays, and (value, key, index) for objects. A plain integer renders a range starting at 1:
<template>
<li v-for="(user, index) in users" :key="user.id">
{{ index + 1 }}. {{ user.name }}
</li>
<li v-for="(value, key) in settings" :key="key">
{{ key }}: {{ value }}
</li>
<span v-for="n in 5" :key="n">{{ n }}</span>
</template>You can use of instead of in to match JavaScript's iterator syntax. v-for also works on <template> to repeat a group of elements without a wrapper.
Vue reuses DOM nodes when a list changes. Without a key, it patches nodes in place by position, which is fast but breaks anything with internal state: an <input> typed into the second row keeps its text even after the second item is removed. A stable key tells Vue which node belongs to which item, so nodes move, insert and delete correctly.
:key="item.id".<TransitionGroup> and to components rendered in a loop.Do not put filtering logic in the template. Compute a derived list instead so the source array stays untouched and the result is cached:
<script setup>
import { ref, computed } from 'vue'
const query = ref('')
const products = ref([
{ id: 1, name: 'Keyboard', price: 49 },
{ id: 2, name: 'Monitor', price: 219 },
{ id: 3, name: 'Mouse', price: 25 }
])
const results = computed(() =>
products.value
.filter(p => p.name.toLowerCase().includes(query.value.toLowerCase()))
.toSorted((a, b) => a.price - b.price)
)
</script>
<template>
<input v-model="query" placeholder="Search" />
<ul>
<li v-for="p in results" :key="p.id">{{ p.name }} - ${{ p.price }}</li>
</ul>
</template>toSorted() returns a new array. If you must support older browsers, use [...arr].sort(); calling .sort() directly inside a computed would reorder the original reactive array.
Vue's reactivity tracks mutating methods such as push, splice and sort on reactive arrays, so both mutation and replacement (items.value = items.value.filter(...)) update the view.
v-if and v-for on the same element. In Vue 3 v-if is evaluated first and cannot see the loop variable. Move v-if to a wrapping <template v-for> or filter with a computed.:key entirely; Vue prints a warning in development and list state can become inconsistent.v-show on a component that should reset its state each time it appears; use v-if instead.Which directive should you use for a dropdown that opens and closes many times per session?
v-if creates and destroys elements; v-show toggles display and is better for frequent toggles.v-else-if and v-else must directly follow their v-if sibling; <template v-if> groups elements without extra markup.v-for iterates arrays, objects and integer ranges, and works on <template>.:key; avoid array indexes when the list changes order.computed property so the source data is never mutated.Next lesson: Class and Style Bindings — toggle CSS classes and inline styles from reactive state with object and array syntax.