Conditional Rendering: v-if and v-show
Vue offers two directives for showing or hiding content based on a condition: v-if and v-show. Although they look interchangeable in simple examples, they operate very differently under the hood, and picking the wrong one for a given situation can cause unnecessary re-renders or wasted DOM nodes. v-if controls whether an element (and its component instance, if any) is actually present in the DOM at all, while v-show always keeps the element in the DOM and merely toggles its CSS display property.
Cricket analogy: v-if is like completely removing a player from the matchday squad sheet when dropped — they're not on the ground at all — while v-show is like benching a player who stays in the dugout, fully present and ready, just not currently on the field of play.
How v-if works
When a v-if condition is falsy, Vue completely destroys the element and any components or directives inside it — event listeners are removed, child component instances are unmounted, and their lifecycle hooks (onUnmounted, etc.) fire. When the condition becomes truthy again, Vue creates a fresh element and, if applicable, a fresh component instance, running mount-time lifecycle hooks again. This makes v-if genuinely expensive to toggle, but cheap to render initially when the condition starts false — nothing is created until needed. v-if also supports v-else-if and v-else for branching between multiple mutually exclusive blocks, and it can be applied to a <template> tag to conditionally group multiple elements without adding an extra wrapper to the DOM.
Cricket analogy: When a v-if condition goes false, it's like a substitute fielder being sent off the ground entirely, needing the whole warm-up routine again (mount lifecycle hooks) to come back on, which is why constantly rotating players in and out is costly, unlike never fielding them at all if they start on the bench; v-else-if/v-else works like a clear batting order, and it can apply to a whole substitution group (a <template> wrapper) without an extra visible slot.
How v-show works
v-show always renders the element into the DOM regardless of the initial condition, then toggles the inline display: none style whenever the condition changes. The element and any child components stay mounted throughout — their state is preserved, and no lifecycle hooks fire on toggle. This makes v-show cheap to toggle repeatedly but more expensive on first render if the condition is initially false, since the element is created and mounted even though it isn't shown. v-show does not support v-else and cannot be used directly on a <template> element, because it needs a concrete DOM node to apply the style to.
Cricket analogy: v-show is like a substitute sitting fully padded up in the dugout the entire match, ready to walk straight onto the field instantly, with no lifecycle hooks firing since they never actually left — cheap to send in and pull back repeatedly, but they had to get padded up upfront even if never called on, and there's no 'else' bench slot since v-show just toggles one player's visibility.
<script setup>
import { ref } from 'vue'
const activeTab = ref('profile')
const isPanelOpen = ref(true)
</script>
<template>
<!-- v-if: branches are mutually exclusive, components fully mount/unmount -->
<section v-if="activeTab === 'profile'">Profile content</section>
<section v-else-if="activeTab === 'settings'">Settings content</section>
<section v-else>No tab selected</section>
<!-- v-show: toggled frequently, stays in the DOM, just hides visually -->
<button @click="isPanelOpen = !isPanelOpen">Toggle panel</button>
<div v-show="isPanelOpen" class="side-panel">
Panel contents stay mounted even when hidden
</div>
</template>This is directly analogous to React's pattern of conditionally returning JSX (equivalent to v-if — nothing renders to the DOM) versus toggling a CSS class or inline style on an always-rendered element (equivalent to v-show). Vue simply bakes both patterns into first-class directives instead of leaving the choice entirely to component logic.
A frequent pitfall is using v-if on content that toggles on every keystroke or scroll event, such as a dropdown that opens and closes rapidly — this repeatedly mounts and unmounts child components, destroying their internal state (like a scroll position or an input's typed text) every time. If content toggles often and preserving internal state matters, v-show is almost always the better choice; reserve v-if for content that is rarely toggled or that should truly not exist in the DOM until needed (e.g., for accessibility or to avoid running expensive child logic).
- v-if adds/removes the element and its component instance from the DOM entirely; v-show only toggles the CSS display property.
- v-if triggers real mount/unmount lifecycle hooks each time the condition flips; v-show never does, because the element stays mounted.
- v-if has a higher toggle cost but a lower initial render cost when the condition starts false.
- v-show has a higher initial render cost (always mounts) but a much lower toggle cost, making it ideal for frequently toggled UI.
- v-if supports v-else-if/v-else branching and works on
<template>wrapper tags; v-show supports neither. - Choose v-if for rarely-toggled or mutually exclusive content, and v-show for UI that toggles often and needs to preserve internal state.
Practice what you learned
1. What happens to a component instance when its v-if condition becomes false?
2. Which directive toggles only the CSS display property while keeping the element mounted?
3. Which directive can be applied to a `<template>` tag to conditionally group multiple elements without an extra wrapper?
4. A dropdown panel opens and closes frequently as the user interacts with it, and its internal scroll position must be preserved between opens. Which directive is the better fit?
5. Which of the following is true about v-if's initial render cost when the condition starts as false?
Was this page helpful?
You May Also Like
List Rendering with v-for
Covers how v-for renders lists and objects in Vue templates, why the key attribute matters, and common performance and correctness pitfalls.
Template Syntax
A tour of Vue's HTML-based template syntax, including text interpolation, directive bindings, and the shorthand notations for v-bind and v-on.
reactive() and ref()
A deep dive into Vue 3's two core reactivity primitives — ref() for any value type and reactive() for objects — and when to use each.
Computed Caching and Performance
Explains how Vue's computed properties memoize their results based on reactive dependencies, and how to use that caching behavior deliberately to avoid wasteful recalculation in components.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics