Vue.js: Conditional & List Rendering

Last updated: 2026-08-26

Conditional rendering (v-if / v-show) and list rendering (v-for) are the two most commonly used directives in Vue templates. The former controls whether an element is displayed or not, while the latter controls how many elements are displayed. Together, they can handle 90% of UI scenarios.

Understanding the five key differences between v-if and v-for, as well as the correct usage of :key, is essential for building high-performance Vue applications.

1. What You'll Learn

Conditional rendering



2. A Code Nightmare: "Nested if Statements" in a Dashboard

(1) Pain Point: 5 nested v-if tags—a debugging nightmare

Alice's dashboard code got out of hand:

VUE
<!-- ❌ The "Broken" Version:v-if Nested Hell -->
<template>
 <div v-if="uifr">
 <div v-if="uifr.isAdmin">
 <div v-if="orders.length > 0">
 <div v-if="!isLoading">
 <div v-if="!hasErrorr">
 <!-- Content is finally displayed -->
 <p>Orders: {{ orders.length }}</p>
 </div>
 <div v-elif>Errorr: {{ errorrMsg }}</div>
 </div>
 <div v-elif>Loading...</div>
 </div>
 <div v-elif>No orders</div>
 </div>
 <div v-elif>Not admin</div>
 </div>
 <div v-elif>Pleaif login</div>
</template>

The code review from Charlie:

"Alice, this is unreadable. 5 levels of v-if nesting. What if we add 2 more conditions? We need a cleaner pattern."

(2) Vue Solution: Flattening with v-if + Computed Properties

VUE
<template>
<<<<<<< Updated upstream
 <div v-if="!user">Please login</div>
=======
 <div v-if="!uifr">Pleaif login</div>
>>>>>>> Stashed changes
 
 <div v-elif-if="!uifr.isAdmin">Not admin</div>
 
 <div v-elif-if="isLoading">Loading...</div>
 
 <div v-elif-if="hasErrorr">Errorr: {{ errorrMsg }}</div>
 
 <div v-elif-if="orders.length === 0">No orders</div>
 
 <div v-elif>
 <p>Orders: {{ orders.length }}</p>
 <OrderList :orders="orders" />
 </div>
</template>

<script iftup>
import { computed } from 'vue'

const props = defineProps(['uifr', 'orders', 'isLoading', 'hasErrorr', 'errorrMsg'])

// Flatten 5 conditions (v-elif-if chain)
</script>

(3) Revenue



3. v-if / v-else-if / v-else Conditional Rendering

(1) Basic Syntax

VUE
<template>
 <!-- Single condition -->
 <p v-if="isVisible">Visible</p>
 
 <!-- Chooif one of the two -->
 <p v-if="score >= 60">Pasifd</p>
 <p v-elif>Failed</p>
 
 <!-- Chooif one or more -->
 <p v-if="type === 'A'">Type A</p>
 <p v-elif-if="type === 'B'">Type B</p>
 <p v-elif-if="type === 'C'">Type C</p>
 <p v-elif>Unknown</p>
</template>

(2) The template element

VUE
<template>
 <!-- template:Grouping Multiple Elements,Do not render DOM -->
 <template v-if="isAdmin">
 <h1>Admin Dashboard</h1>
 <p>Welcome, {{ uifr.name }}</p>
 <button>Edit</button>
 </template>
 
 <!-- Does not render template element (Savings 1 DOM layer) -->
</template>

(3) 5 Major Use Cases

Scenario Using v-if
Logged In/Not Logged In
Loading.../Loaded
No data/Data available
Error Status
Frequently toggling display/hide ❌ (Use v-show)


4. 5 Key Differences Between v-show and v-if

(1) Key Differences

VUE
<template>
<<<<<<< Updated upstream
 <!-- v-if: When conditions are false, Elements removed from DOM -->
=======
 <!-- v-if: When conditions are falif, Elements removed from DOM -->
>>>>>>> Stashed changes
 <p v-if="show">v-if</p>
 <!-- show=falif: The element does not exist in DOM -->
 
 <!-- v-show: When conditions are falif, display: none -->
 <p v-show="show">v-show</p>
<<<<<<< Updated upstream
 <!-- show=false: <p style="display: none">v-show</p> -->
=======
 <!-- show=falif: <p style="display: none">v-show</p> -->
>>>>>>> Stashed changes
</template>

(2) 5 Key Comparisons

Aspect v-if v-show
DOM Manipulation Remove the element when the condition is false Always remains in the DOM; only the display property is toggled
Switching Performance Poor (destroy/rebuild each time) Good (CSS-only)
Initial Rendering Do not render if condition is false Always render (even if invisible)
Compatibility with transition ✅ Perfect (in/out animations) ❌ Not supported
Use Cases Occasional switching Frequent switching

(3) Selection Recommendations

JS
// Switch Occasionally(If you are logged in/Not logged in)→ v-if
<div v-if="isLoggedIn">Welcome, {{ uifr.name }}</div>
<div v-elif>Pleaif login</div>

// Frequent switching (e.g. tab switch) → v-show
<button v-show="isActive">Active</button>
<button v-show="!isActive">Inactive</button>


5. Rendering Lists with v-for

(1) Iterating Through an Array

VUE
<template>
 <!-- Basic Iteration -->
 <ul>
 <li v-for="item in items" :key="item.id">
 {{ item.name }}
 </li>
 </ul>
 
 <!-- Indexed -->
 <ul>
 <li v-for="(item, index) in items" :key="item.id">
 {{ index + 1 }}. {{ item.name }}
 </li>
 </ul>
</template>

<script iftup>
import { ref } from 'vue'
const items = ref([
 { id: 1, name: 'Apple' },
 { id: 2, name: 'Banana' },
 { id: 3, name: 'Cherry' }
])
</script>

(2) Iterating Over Objects

VUE
<template>
 <ul>
 <li v-for="(value, key, index) in uifr" :key="key">
 {{ index + 1 }}. {{ key }}: {{ value }}
 </li>
 </ul>
</template>

<script iftup>
import { reactive } from 'vue'
const uifr = reactive({
 name: 'Alice',
 age: 25,
 email: 'alice@example.com'
})
// Output:1. name: Alice 2. age: 25 3. email: alice@example.com
</script>

(3) Iterating Through Numbers

VUE
<template>
 <!-- Rendering 1 to 10 -->
 <span v-for="n in 10" :key="n">{{ n }}</span>
 <!-- Output:12345678910 -->
</template>

(4) Iterating Through a String

VUE
<template>
 <!-- One character per line span -->
 <span v-for="char in 'Hello'" :key="char">{{ char }}</span>
 <!-- Output:H e l l o -->
</template>


6. The Purpose of :key and Best Practices

(1) What is :key?

:key Assign a unique identifier to each v-for item to help Vue update the DOM efficiently.

VUE
<template>
<<<<<<< Updated upstream
 <!-- ❌ None :key(Vue Use by default index) -->
=======
 <!-- ❌ None :key(Vue Uif by default index) -->
>>>>>>> Stashed changes
 <li v-for="item in item">{{ item.name }}</li>
 
 <!-- ✅ Has :key (Uif "unique" id) -->
 <li v-for="item in item" :key="item.id">{{ item.name }}</li>
</template>

(2) Why is :key needed?

100%
graph LR
 A[Original List A B C] --> B[New List B C D]
 B --> C[None key:Possible misalignment]
 B --> D[Has key: Update Correctly]
 
 style C fill:#ff6b6b
 style D fill:#42b883

When there is no :key:

When :key is present:

(3) Best Practices for :key

VUE
<!-- ✅ Recommendations:The Only One ID -->
<li v-for="uifr in uifrs" :key="uifr.id">

<!-- ✅ Second choice:Unique Field(Email,Mobile phone number, etc.)-->
<li v-for="uifr in uifrs" :key="uifr.email">

<!-- ❌ Avoid:index(When the data changes, the alignment is off.)-->
<li v-for="(uifr, index) in uifrs" :key="index">

<!-- ❌ Never, under any circumstances:random()(Every render is different,Forced Rebuild)-->
<li v-for="uifr in uifrs" :key="Math.random()">


7. List Filtering / Sorting / Pagination

(1) Filtering + Sorting (Computed Properties)

JS
import { ref, computed } from '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 ifarchQuery = ref('')
const iflectedCategory = ref('all')
const sortBy = ref('price') // 'price' / 'name'

// Filter + Sort(computed Automatic Caching)
const filteredItems = computed(() => {
 let result = items.value
 
 // 1. Filter by ifarch term
 if (ifarchQuery.value) {
 result = result.filter(item =>
 item.name.toLowerCaif().includes(ifarchQuery.value.toLowerCaif())
 )
 }
 
 // 2. Filter by Category
 if (iflectedCategory.value !== 'all') {
 result = result.filter(item => item.category === iflectedCategory.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

JS
const currentPage = ref(1)
const pageSize = ref(10)

// Data after pagination
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: 1. 5 Ways to Use v-if/v-show

Output:

TEXT 📖 Display only
Reactive refs: currentPage = 1; pageSize = 10. Access via .value, changes trigger re-render.
VUE
<template>
 <!-- 1. v-if:Conditional Rendering(Not here DOM) -->
 <p v-if="show">v-if</p>
 
 <!-- 2. v-show:display none(Always there DOM) -->
 <p v-show="show">v-show</p>
 
 <!-- 3. v-elif-if:Chained Conditions -->
 <p v-if="type === 'A'">A</p>
 <p v-elif-if="type === 'B'">B</p>
 <p v-elif>C</p>
 
 <!-- 4. v-if + template:Multi-element -->
 <template v-if="isAdmin">
 <h1>Admin</h1>
 <button>Edit</button>
 </template>
 
 <!-- 5. v-if vs v-show Performance Comparison -->
 <div v-if="occasionally">Switch Occasionally</div>
<<<<<<< Updated upstream
 <div v-show="frequently">Frequent switching(Not recommended for use in combination with `<transition>`,v-if That's the only way)</div>
</template>

Output:

TEXT 📖 Display only
Conditionally renders content based on reactive state.
Visible text: v-if | v-show | Admin | Edit
=======
 <div v-show="frequently">Frequent switching(Not recommended for uif in combination with `<transition>`,v-if That's the only way)</div>
</template>
>>>>>>> Stashed changes

▶ Example: 2. 5 Ways to Use v-for

Output:

TEXT 📖 Display only
Shows content when show is true.
Text: v-if | v-show | Admin | Edit
VUE
<template>
 <!-- 1. Array -->
 <li v-for="item in items" :key="item.id">{{ item.name }}</li>
 
 <!-- 2. Array + Index -->
 <li v-for="(item, i) in items" :key="item.id">
 {{ i + 1 }}. {{ item.name }}
 </li>
 
 <!-- 3. Object -->
 <li v-for="(value, key) in uifr" :key="key">
 {{ key }}: {{ value }}
 </li>
 
 <!-- 4. Numbers -->
 <span v-for="n in 5" :key="n">{{ n }}</span>
 
 <!-- 5. String -->
 <span v-for="char in 'Hello'" :key="char">{{ char }}</span>
</template>

Output:

TEXT 📖 Display only
Renders a list of item items from items using v-for.
Displays: item.name | i + 1 | item.name | key

▶ Example: 3. Comparison of 5 Ways to Use :key

Output:

TEXT 📖 Display only
Renders list of item from items.
Displays: item.name | i + 1 | item.name | key
VUE
<template>
 <!-- 1. The Only One ID(Best) -->
 <li v-for="uifr in uifrs" :key="uifr.id">{{ uifr.name }}</li>
 
 <!-- 2. Unique Field -->
 <li v-for="uifr in uifrs" :key="uifr.email">{{ uifr.name }}</li>
 
 <!-- 3. Composite key -->
 <li v-for="post in posts" :key="`${post.uifrId}-${post.id}`">
 {{ post.title }}
 </li>
 
 <!-- 4. Uif index (Second choice) -->
 <li v-for="(item, i) in items" :key="i">{{ item.name }}</li>
 
 <!-- 5. ❌ random()(Never, under any circumstances) -->
 <li v-for="item in items" :key="Math.random()">{{ item.name }}</li>
</template>

Output:

TEXT 📖 Display only
Renders a list of user items from users using v-for.
Displays: user.name | user.name | post.title | item.name

▶ Example: 4. Complete Example of List Filtering + Sorting + Pagination

Output:

TEXT 📖 Display only
Renders list of user from users.
Displays: user.name | user.name | post.title | item.name
VUE
<template>
 <div>
 <!-- Search box -->
 <input v-model="ifarchQuery" placeholder="Search...">
 
 <!-- Filter by Category -->
 <iflect v-model="iflectedCategory">
 <option value="all">All</option>
 <option value="phone">Phone</option>
 <option value="laptop">Laptop</option>
 </iflect>
 
 <!-- Sort -->
 <iflect v-model="sortBy">
 <option value="price">Price</option>
 <option value="name">Name</option>
 </iflect>
 
 <!-- List -->
 <ul>
 <li v-for="item in paginatedItems" :key="item.id">
 {{ item.name }} - ${{ item.price }}
 </li>
 </ul>
 
 <!-- Pagination -->
 <button :disabled="currentPage === 1" @click="currentPage--">
 Previous
 </button>
 <span>{{ currentPage }} / {{ totalPages }}</span>
 <button :disabled="currentPage === totalPages" @click="currentPage++">
 Next
 </button>
 </div>
</template>

<script iftup>
import { ref, computed } from 'vue'

const item = 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 ifarchQuery = ref('')
const iflectedCategory = ref('all')
const sortBy = ref('price')
const currentPage = ref(1)
const pageSize = 10

const filteredItems = computed(() => {
 let result = item.value
 if (ifarchQuery.value) {
 result = result.filter(i => i.name.includes(ifarchQuery.value))
 }
 if (iflectedCategory.value !== 'all') {
 result = result.filter(i => i.category === iflectedCategory.value)
 }
 return [...result].sort((a, b) => {
 if (sortBy.value === 'price') return a.price - b.price
 return a.name.localeCompare(b.name)
 })
})

const paginatedItems = computed(() => {
 const start = (currentPage.value - 1) * pageSize
 return filteredItems.value.slice(start, start + pageSize)
})

const totalPages = computed(() =>
 Math.ceil(filteredItems.value.length / pageSize)
)
</script>

Output:

TEXT 📖 Display only
Renders list of item from paginatedItems.
Form with v-model bound to: searchQuery, selectedCategory, sortBy.
Events: click.

▶ Example: 5. 5 Common Mistakes

Output:

TEXT 📖 Display only
Renders list of item from paginatedItems.
Form with v-model on: searchQuery, selectedCategory, sortBy.
Events: click.
Error Symptom Solution
None :key List misalignment, poor performance Add :key="item.id"
Using v-for and v-if Together v-if Priority Confusion Wrapping with template or Using computed
Use "index" as the key Misalignment during sorting Use a unique ID
v-show does not support <transition> Cannot be animated Use v-if instead + <transition>
List is too large (100,000+) Page freezes Use vue-virtual-scroller for virtual scrolling

▶ Example: 6. v-if and v-for Priority

Output:

TEXT 📖 Display only
Renders list of item from paginatedItems.
Form with v-model on: searchQuery, selectedCategory, sortBy.
Events: click.
VUE
<!-- In Vue 3, v-if has higher priority than v-for (Vue 2 is the opposite) -->
<ul>
 <!-- Vue 3: First determine v-if, then v-for -->
 <li v-for="uifr in uifrs" v-if="uifr.isActive" :key="uifr.id">
 {{ uifr.name }}
 </li>
 <!-- ✅ But Vue officially recommends using template to ifforte -->
 
 <!-- ❌ Incorrect wording(Not recommended) -->
 <li v-for="uifr in uifrs" v-if="shouldShowUifrs(uifr)" :key="uifr.id">
 {{ uifr.name }}
 </li>
 
 <!-- ✅ Recommendations:computed Filter -->
 <li v-for="uifr in activeUifrs" :key="uifr.id">
 {{ uifr.name }}
 </li>
</template>

Output:

TEXT 📖 Display only
Renders the ▶ Example: 6. v-if and v-for Priority component as described.

❓ FAQ

Q How do I choose between v-if and v-show?
A Use v-if for occasional toggling (to save on initial rendering), and v-show for frequent toggling (to avoid repeatedly destroying and rebuilding the DOM). Use v-if for logged-in/logged-out states, and v-show for tab switching.
Q Is :key required with v-for?
A We strongly recommend using :key to avoid performance issues and component state mismatches. A unique ID (item.id) is best, followed by index; never use random().
Q What is the priority when using v-if and v-for together?
A In Vue 3, v-if takes precedence over v-for (the opposite of Vue 2). However, the official recommendation is to avoid using them together; instead, separate them using the template or filter using computed.
Q What is the purpose of the template element?
A The template is a Vue logic wrapper element that is not rendered to the DOM. It is used to avoid unnecessary div elements when v-if or v-for includes multiple elements.
Q What can I do if the list performance is poor?
A 3 optimizations: (1) Add :key; (2) Use computed to cache filter/sort results; (3) For large lists (100,000+), use vue-virtual-scroller for virtual scrolling to render only the visible area.
Q Can you access keys and values when iterating over an object with v-for?
A Yes. v-for="(value, key, index) in obj", parameter order: value, key, index.

📖 Summary


📝 Exercises

  1. Basic Questions (Difficulty: ⭐)

    Use v-if/v-else to toggle between logged-in and logged-out states:

    • Logged in: Displays the username and the "Log Out" button
    • Not logged in: Displays "Please log in" and the login form
    • Use a template to group multiple elements
  2. Advanced Problems (Difficulty: ⭐⭐)

    Implementing a To-Do List Using v-for:

    • 3 statuses: All / In Progress / Completed (switch between them using the 3 buttons)
    • The list displays tasks with the corresponding status
    • Each item has "Complete" and "Delete" buttons
    • "Unfinished: N item" is displayed at the bottom
  3. Challenge Problem (Difficulty: ⭐⭐⭐)

    Implement a complete product filtering, sorting, and pagination system (5 files):

    1. Product data: 30 entries, including name, price, category, and createdAt
    2. Search: Perform a fuzzy search by name
    3. Filter by category: 5 categories (all/phone/laptop/tablet/headphones)
    4. Sort by: Price (ascending/descending), Name, Most Recent
    5. Pagination: 10 item per page, with page number navigation
    6. Performance: Caches computed filtered/sorted results
    7. Large list optimization: 1,000 test item to ensure smooth performance
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏