Conditional Rendering
v-if vs v-show
Vue provides two ways to conditionally show elements. Choose based on how often the condition changes:
| Feature | v-if | v-show |
|---|---|---|
| DOM presence | Removed/added from DOM | Always in DOM (display:none) |
| Initial render cost | Lower (if false) | Higher (always renders) |
| Toggle cost | Higher (destroy/create) | Lower (CSS only) |
| Works with v-else | Yes | No |
| Best for | Rarely toggled conditions | Frequently toggled visibility |
<template>
<div>
<!-- v-if / v-else-if / v-else -->
<div v-if="status === 'loading'">
<span class="spinner"></span> Loading...
</div>
<div v-else-if="status === 'error'">
<p class="error">{{ errorMessage }}</p>
<button @click="retry">Retry</button>
</div>
<div v-else-if="status === 'empty'">
<p>No data found.</p>
</div>
<div v-else>
<!-- data is ready -->
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</div>
<!-- v-show — stays in DOM, toggles display -->
<div v-show="isMenuOpen" class="dropdown-menu">
<a href="#">Profile</a>
<a href="#">Settings</a>
<a href="#">Logout</a>
</div>
<button @click="isMenuOpen = !isMenuOpen">Menu</button>
<!-- <template> with v-if — no extra DOM element -->
<template v-if="isAdmin">
<h3>Admin Section</h3>
<p>Only admins see this.</p>
<button>Manage Users</button>
</template>
<!-- Conditional class/style -->
<button
:class="['btn', isActive ? 'btn-primary' : 'btn-secondary']"
:disabled="isLoading"
>
{{ isLoading ? 'Saving...' : 'Save' }}
</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const status = ref('loading') // 'loading' | 'error' | 'empty' | 'success'
const errorMessage = ref('Failed to fetch data')
const items = ref([{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }])
const isMenuOpen = ref(false)
const isAdmin = ref(true)
const isActive = ref(true)
const isLoading = ref(false)
function retry() { status.value = 'loading' }
</script>
<!-- Dynamic Components — <component :is="..."> -->
<template>
<div>
<!-- Tab navigation -->
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.name"
@click="currentTab = tab.name"
:class="{ active: currentTab === tab.name }"
>
{{ tab.label }}
</button>
</div>
<!-- Dynamic component — renders the active tab component -->
<component :is="currentTabComponent" v-bind="tabProps" />
<!-- KeepAlive — cache inactive components (preserve state) -->
<KeepAlive :include="['HomeTab', 'ProfileTab']" :max="3">
<component :is="currentTabComponent" />
</KeepAlive>
<!-- Dynamic component with string name (globally registered) -->
<component :is="'BaseButton'" label="Click me" />
<!-- Conditional component rendering -->
<component
:is="user.role === 'admin' ? AdminDashboard : UserDashboard"
:user="user"
/>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'
import SettingsTab from './tabs/SettingsTab.vue'
import AdminDashboard from './AdminDashboard.vue'
import UserDashboard from './UserDashboard.vue'
const currentTab = ref('home')
const user = ref({ role: 'admin', name: 'Alice' })
const tabs = [
{ name: 'home', label: 'Home', component: HomeTab },
{ name: 'profile', label: 'Profile', component: ProfileTab },
{ name: 'settings', label: 'Settings', component: SettingsTab },
]
const currentTabComponent = computed(() =>
tabs.find(t => t.name === currentTab.value)?.component
)
const tabProps = computed(() => ({
userId: 1,
// other shared props
}))
</script>
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.