Vue.js: Conditional & List Rendering
Last updated: 2026-10-20
Conditional rendering (v-if / v-show) and list rendering (v-for) are the two most frequently used directives in Vue templates. The first controls "whether to show it", the second controls "how many to show". Combined, they can render 90% of UI scenarios.
Mastering the 5 key differences between v-if and v-for, along with the correct usage of :key, is what lets you write high-performance Vue applications.
1. What You'll Learn
v-if/v-else-if/v-elseconditional rendering- The 5 key differences between
v-showandv-if - Iterating arrays, objects, numbers, and strings with
v-for - What
:keydoes and the best practices for using it - Wrapping multiple elements with the
templateelement - Filtering, sorting, and paginating lists
- Virtual scrolling optimization (10,000+ items)
2. The "Nested if" Code Disaster in a Dashboard
(1) Pain Point: 5 Nested v-ifs, a Debugging Nightmare
Alice's dashboard code has spiraled out of control:
Charlie's code review comment:
"Alice, this is simply unreadable. Five levels of nested v-if. What happens when we add 2 more conditions? We need a clearer pattern."
(2) The Vue Solution: Flatten the v-ifs + Computed Properties
(3) Benefits
- Readability: 5 levels of nesting → 6 flat lines
- Maintainability: adding a condition means adding a single v-else-if line
- Debugging: each line holds one independent condition, so breakpoints are easy to place
3. v-if / v-else-if / v-else Conditional Rendering
(1) Basic Syntax
(2) The template Element
(3) 5 Usage Scenarios
| Scenario | Use v-if |
|---|---|
| Logged in / not logged in | ✅ |
| Loading / load complete | ✅ |
| Empty data / has data | ✅ |
| Error state | ✅ |
| Toggling visibility frequently | ❌ (use v-show) |
▶ Example: 5 Ways to Write v-if/v-show
▶ Try it YourselfOutput:
Six pieces of content plus two toggle buttons.
The first two lines are visible because show is true; 3.
Type A shows because type starts as 'A'; the admin block renders <h1>4.
Admin Panel</h1> with an Edit button;
5a is hidden (its v-if is false) while 5b is visible.
Clicking Toggle show removes line 1 from the DOM and sets display: none on line 2;
clicking Toggle type swaps between 3. Type A and 3. Type B.
1. v-if visible (click to toggle)
2. v-show visible
3. Type A
4. Admin Panel
[Edit]
5b. Frequent toggle (v-show)
[Toggle show] [Toggle type]
4. The 5 Key Differences Between v-show and v-if
(1) Core Difference
(2) 5 Comparisons
| Dimension | v-if | v-show |
|---|---|---|
| DOM operation | Removes the element when the condition is false | Always in the DOM, only toggles display |
| Toggle performance | Poor (destroy/rebuild each time) | Good (only swaps CSS) |
| Initial render | Nothing rendered when the condition is false | Always rendered (even when invisible) |
| With transition | ✅ Perfect (enter/leave animation) | ❌ Not supported |
| Best for | Occasional toggling | Frequent toggling |
(3) How to Choose
// Occasional toggling (logged in / logged out) → v-if
<div v-if="isLoggedIn">Welcome, {{ user.name }}</div>
<div v-else>Please login</div>
// Frequent toggling (tab switching) → v-show
<button v-show="isActive">Active</button>
<button v-show="!isActive">Inactive</button>
5. List Rendering with v-for
(1) Iterating Arrays
(2) Iterating Objects
(3) Iterating Numbers
(4) Iterating Strings
▶ Example: 5 Ways to Write v-for
▶ Try it YourselfOutput:
Five sections rendered in order — a plain array list (Apple / Banana / Cherry), the same list with 1.–3.
prefixes, the object rendered as name: Alice / age: 25 / email: alice@example.com, the numbers [1][2][3][4][5], and one span per character of Hello.
1. Array
Apple
Banana
Cherry
2. Array + index
1. Apple
2. Banana
3. Cherry
3. Object
name: Alice
age: 25
email: alice@example.com
4. Number
[1][2][3][4][5]
5. String
H e l l o
▶ Example: v-if and v-for Priority
▶ Try it YourselfOutput:
The list starts with a single row, Alice, plus the button Toggle Alice status.
Clicking it flips isActive on the user with id === 1, so the row disappears;
clicking again brings Alice back.
Bob never appears because the computed property filters him out before v-for runs.
Alice
[Toggle Alice status]
6. What :key Does and Best Practices
(1) What Is :key?
:key gives every v-for item a unique identity, which helps Vue update the DOM efficiently.
(2) Why Do We Need :key?
graph LR
A[Original list A B C] --> B[New list B C D]
B --> C[No key: nodes may get mismatched]
B --> D[With key: correct updates]
style C fill:#ff6b6b
style D fill:#42b883
Without :key:
- Vue compares by index
- After A is deleted, B/C become (0)/(1), but Vue believes "B/C are still there"
- This leads to mismatched component state
With :key:
- Vue compares by id
- It identifies exactly what was added/removed/moved
- Performance is optimal
(3) :key Best Practices
<!-- ✅ Recommended: unique ID -->
<li v-for="user in users" :key="user.id">
<!-- ✅ Second choice: a unique field (email, phone number, etc.) -->
<li v-for="user in users" :key="user.email">
<!-- ❌ Avoid: index (items get mismatched when data changes) -->
<li v-for="(user, index) in users" :key="index">
<!-- ❌ Never: random() (different on every render, forces a rebuild) -->
<li v-for="user in users" :key="Math.random()">
▶ Example: Comparing 5 Ways to Use :key
▶ Try it YourselfOutput:
Five lists that look identical on the first render — Alice / Bob twice, Post A / Post B, then X / Y twice.
The difference only shows up on updates: keys 1–3 stay bound to the right data, index can shift content when the list is reordered, and Math.random() produces a new key on every render, so Vue tears down and rebuilds every node.
1. Unique ID (best)
Alice
Bob
2. Unique field
Alice
Bob
3. Composite key
Post A
Post B
4. Using index (second choice)
X
Y
5. ❌ random() (never)
X
Y
7. List Filtering / Sorting / Pagination
(1) Filtering + Sorting (Computed Properties)
const { ref, computed } = Vue
const items = ref([
{ id: 1, name: 'iPhone', price: 999, category: 'phone' },
{ id: 2, name: 'MacBook', price: 2499, category: 'laptop' },
{ id: 3, name: 'iPad', price: 599, category: 'tablet' }
])
const searchQuery = ref('')
const selectedCategory = ref('all')
const sortBy = ref('price') // 'price' / 'name'
// Filtering + sorting (computed caches automatically)
const filteredItems = computed(() => {
let result = items.value
// 1. Filter by search term
if (searchQuery.value) {
result = result.filter(item =>
item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
}
// 2. Filter by category
if (selectedCategory.value !== 'all') {
result = result.filter(item => item.category === selectedCategory.value)
}
// 3. Sort
result = [...result].sort((a, b) => {
if (sortBy.value === 'price') return a.price - b.price
if (sortBy.value === 'name') return a.name.localeCompare(b.name)
return 0
})
return result
})
(2) Pagination
const currentPage = ref(1)
const pageSize = ref(10)
// Data for the current page
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return filteredItems.value.slice(start, start + pageSize.value)
})
// Total number of pages
const totalPages = computed(() =>
Math.ceil(filteredItems.value.length / pageSize.value)
)
8. Complete Example: Dynamic Product List
▶ Example: Complete Filtering + Sorting + Pagination Example
▶ Expand runnable example (67 lines)
Output:
A search box, two selects, three rows sorted by price ascending (iPad - $599, iPhone - $999, MacBook - $2499), and a pager reading 1 / 1 with both buttons disabled.
Typing i narrows the list, choosing phone keeps only iPhone, and switching the sort to Name reorders the rows alphabetically — all without touching the original items array.
[Search...] [All v] [Price v]
iPad - $599
iPhone - $999
MacBook - $2499
[Previous] 1 / 1 [Next]
(1) 📊 5 Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| No :key | Items get mismatched, poor performance | Add :key="item.id" |
| Using v-for and v-if together | Confusing v-if priority | Wrap with template or use computed |
| Using index as the key | Items get mismatched when sorting | Use a unique ID |
v-show does not support <transition> |
No animation | Switch to v-if + <transition> |
| The list is too large (100k+) | Page freezes | Use vue-virtual-scroller for virtual scrolling |
❓ FAQ
item.id) is best, index is second, and random() should never be used.v-for="(value, key, index) in obj" — the parameter order is value, key, index.<div v-if="items.length">list</div><div v-else>No data</div>.@click="activeIndex = item.id" :class="{ active: activeIndex === item.id }".📖 Summary
- v-if renders conditionally (removes from the DOM); v-show toggles display (always in the DOM)
- Use v-if for occasional toggling, v-show for frequent toggling
- v-for iterates arrays/objects/numbers/strings; use :key to improve performance
- :key is best with a unique ID, then index, and never random()
- The template element wraps multiple elements without rendering to the DOM
- Filtering/sorting/pagination for lists is cached automatically by computed
- In Vue 3, v-if has a higher priority than v-for
- Optimize large lists with virtual scrolling (100k+ items)
📝 Exercises
-
Basic (difficulty ⭐) Implement a logged-in / logged-out switch with v-if/v-else:
- Logged in: show the username and a "Logout" button
- Logged out: show "Please log in" and the login form
- Wrap multiple elements with template
-
Intermediate (difficulty ⭐⭐) Build a Todo list with v-for:
- 3 states: All / Active / Completed (switched by 3 buttons)
- The list shows tasks matching the current state
- Each item has "Complete" and "Delete" buttons
- The footer shows "Active: N items"
-
Challenge (difficulty ⭐⭐⭐) Build a complete product filter + sort + pagination system (5 files):
- Product data: 30 items with name/price/category/createdAt
- Search: fuzzy match on name
- Category filter: 5 categories (all/phone/laptop/tablet/headphone)
- Sorting: price ascending/descending/name/newest
- Pagination: 10 items per page with page-number navigation
- Performance: cache the filter/sort results with computed
- Large-list optimization: test with 1000 items and make sure it stays smooth