Vue.js 8 🟢 Element Toggling with v-show vs Conditional DOM Mounting
Hiding an element is not the same as removing it. This distinction, simple as it sounds, sits at the heart of one of Vue’s most consequential architectural choices. Two directives—v-if and v-show—both make elements appear and disappear from the user’s view. But underneath that superficial similarity, they do fundamentally different things to the DOM, to component lifecycle, and to the performance profile of your application. Choosing the wrong one is not a syntax error; it is a silent drag on efficiency or a subtle source of state bugs.
This chapter examines v-show in detail, contrasting it against the “real conditional rendering” of v-if that you learned in the previous chapter. You will learn how v-show manipulates the CSS display property, why it has no equivalent of v-else, and how its performance characteristics invert depending on how often your condition changes. The goal is not to declare one directive superior but to equip you with a clear mental model for when each one is the right tool.
By the end, you will understand why toggling a form with v-if can trigger a browser warning, why a large component inside a frequently-toggled block behaves differently under each directive, and how to decide which one to reach for in your own code.
Key point: v-show never removes an element from the DOM. It toggles the CSS display property, leaving the element, its event listeners, and any child component instances alive and preserved in memory. This makes toggling cheap but the initial render expensive, and it means state persists across visibility changes.
Why v-show exists
The cost of destruction. Real conditional rendering with v-if is powerful, but it has a price. Every time the condition flips from true to false, Vue runs a full teardown: child components are destroyed, event listeners are removed, and the DOM nodes are discarded. When the condition flips back, everything must be rebuilt from scratch . For a complex component with a deep tree, this compile-and-mount cycle can be expensive, and it happens every single time the user toggles the content. v-show exists because not every toggle needs that level of commitment.
The state preservation problem. When v-if destroys a component, it also destroys the component’s internal state. A form inside a v-if block loses any partially-filled data when hidden. A video player resets. A scroll position is forgotten. Sometimes this is desirable—a fresh start each time—but often it is not. v-show preserves state because the element never leaves the DOM . The component instance stays alive, the user’s input remains, and the next reveal shows exactly what was there before. This persistence is not a side effect; it is the direct consequence of the CSS-based approach.
The toggling frequency signal. The Vue documentation is explicit on this point: prefer v-show when you need to toggle something very often, and prefer v-if when the condition is unlikely to change at runtime . The reasoning is pure economics. v-if pays a high cost on every toggle but nothing when the condition is false at initial render. v-show pays the full cost upfront—compiling and rendering the element regardless of the initial condition—but then toggles almost for free . If a user clicks a dropdown ten times in a session, v-show wins. If a modal appears once, v-if is lighter.
The CSS simplicity. Underneath, v-show does exactly one thing: it sets the element’s inline display style to none when the condition is falsy, and restores the original display value when it is truthy . There is no virtual DOM diffing, no component teardown, no recompilation. The browser’s rendering engine handles the show/hide natively. This simplicity is the source of both its speed advantage in toggling and its limitation: it cannot participate in the branching logic of v-else, and it cannot work on <template> elements, because those elements do not have a real DOM node whose display property could be toggled .
The semantic difference matters. When you use v-if on a form, and the form is hidden, it is disconnected from the document. The browser may warn “Form submission canceled because the form is not connected” if a submission is attempted while the form is hidden . With v-show, the form remains connected; only its visibility changes. This is a concrete example of how the “removed vs. hidden” distinction produces observable behavior differences beyond performance.
a. Basic v-show syntax and behavior
The syntax of v-show mirrors v-if: an expression evaluated for truthiness, with the element’s visibility determined by the result.
<template>
<h1 v-show="isVisible">Hello, visibility!</h1>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(true)
</script>
When isVisible is true, the element appears normally. When it becomes false, Vue sets display: none on the element. The element remains in the DOM; you can inspect it in your browser’s developer tools and find it there, hidden but present . This is the critical observable difference from v-if.
The expression can be any valid JavaScript expression. A comparison, a computed property, a method call—all work as expected.
<div v-show="items.length > 0">You have items.</div>
<div v-show="status === 'active'">Active</div>
What you cannot do with v-show is chain it with v-else or v-else-if. The Vue documentation is unambiguous: “v-show doesn’t support the <template> element, nor does it work with v-else” . This is not an arbitrary limitation. The v-else directive relies on the conditional rendering infrastructure that v-if establishes. v-show never “renders” or “does not render” an element in the Vue sense; it only toggles a CSS property. There is no branch to fall back from, because there is no branch—there is just one element whose visibility changes .
b. The <template> limitation
A common pattern with v-if is wrapping multiple elements in a <template> tag so they can be toggled as a group. Attempting the same with v-show will not work.
<!-- This does nothing useful -->
<template v-show="showGroup">
<h2>Title</h2>
<p>Body text</p>
</template>
The <template> element does not correspond to a real DOM node. It is a compile-time wrapper that Vue uses to group multiple elements under a single directive. When v-if is applied to a <template>, Vue knows to add or remove the contained elements as a unit. But v-show needs a real DOM element whose display property it can manipulate. Since <template> has no such property, the directive has no effect .
The workaround is to apply v-show to each child element individually, or to wrap the group in a real <div> and apply v-show to that wrapper. The <div> approach introduces an extra DOM node, which is why v-if with <template> is often preferred for group toggling despite the higher toggle cost.
c. Performance characteristics and the trade-off
The performance profile of v-show and v-if is not a matter of one being “faster” in absolute terms. It is a trade-off between initial cost and toggle cost .
Initial render. v-show always compiles and renders its element, regardless of the initial condition. If the condition is false on mount, the element is rendered and then immediately hidden with display: none . v-if is lazy: if the condition is false, it does nothing at all. The block is not compiled, not mounted, and not present in the virtual DOM. This makes v-if cheaper for content that starts hidden and may never be shown.
Toggling. Once mounted, v-show toggles by flipping a CSS property. This is nearly instantaneous. v-if toggling triggers a full teardown and rebuild cycle. Child components are destroyed and recreated; event listeners are removed and reattached; the DOM subtree is discarded and reconstructed . For a complex block, this can produce noticeable lag on each toggle.
The decision rule. The Vue team’s guidance is straightforward: use v-show if the condition changes frequently, v-if if it rarely changes . A dropdown menu that opens and closes dozens of times per session is a candidate for v-show. A modal dialog that appears once during a workflow is a candidate for v-if. A tooltip that follows the mouse is almost certainly v-show. An error page for a 404 route is v-if.
The trade-off also affects memory. A hidden v-show element still occupies memory: its component instance, its reactive state, its watchers and computed properties all remain active. A v-if element that is not rendered consumes nothing. For large, rarely-visible components, v-if frees resources that v-show would hold indefinitely.
Complete Example Session
<!-- ============================================ -->
<!-- PART 1: BASIC v-show TOGGLE -->
<!-- ============================================ -->
<template>
<button @click="isVisible = !isVisible">Toggle</button>
<p v-show="isVisible">Now you see me</p>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(true)
</script>
<!-- ============================================ -->
<!-- PART 2: v-show WITH COMPUTED CONDITION -->
<!-- ============================================ -->
<template>
<div v-show="hasItems">Items are present</div>
</template>
<script setup>
import { computed, ref } from 'vue'
const items = ref([])
const hasItems = computed(() => items.value.length > 0)
</script>
<!-- ============================================ -->
<!-- PART 3: v-if FOR COMPARISON -->
<!-- ============================================ -->
<template>
<div v-if="isVisible">Conditionally rendered</div>
<div v-else>Fallback content</div>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(false)
</script>
<!-- ============================================ -->
<!-- PART 4: v-show PRESERVES STATE -->
<!-- ============================================ -->
<template>
<button @click="showForm = !showForm">Toggle Form</button>
<form v-show="showForm">
<input v-model="name" placeholder="Name">
</form>
</template>
<script setup>
import { ref } from 'vue'
const showForm = ref(true)
const name = ref('')
// Typing a name, hiding the form, and showing it again
// preserves the typed value.
</script>
<!-- ============================================ -->
<!-- PART 5: v-if DESTROYS STATE -->
<!-- ============================================ -->
<template>
<button @click="showForm = !showForm">Toggle Form</button>
<form v-if="showForm">
<input v-model="name" placeholder="Name">
</form>
</template>
<script setup>
import { ref } from 'vue'
const showForm = ref(true)
const name = ref('')
// Typing a name, hiding the form, and showing it again
// clears the input because the component is destroyed.
</script>
<!-- ============================================ -->
<!-- PART 6: FREQUENT TOGGLE WITH v-show -->
<!-- ============================================ -->
<template>
<div @mouseenter="isHovered = true" @mouseleave="isHovered = false">
Hover me
<div v-show="isHovered">Tooltip content</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isHovered = ref(false)
// Tooltips toggle constantly; v-show is the right choice.
</script>
<!-- ============================================ -->
<!-- PART 7: RARE TOGGLE WITH v-if -->
<!-- ============================================ -->
<template>
<button @click="showModal = true">Open Modal</button>
<Modal v-if="showModal" @close="showModal = false" />
</template>
<script setup>
import { ref } from 'vue'
import Modal from './Modal.vue'
const showModal = ref(false)
// Modal appears once per workflow; v-if is appropriate.
</script>
<!-- ============================================ -->
<!-- PART 8: v-show ON COMPONENT ROOT -->
<!-- ============================================ -->
<template>
<UserProfile v-show="showProfile" :user="user" />
</template>
<script setup>
import { ref } from 'vue'
import UserProfile from './UserProfile.vue'
const showProfile = ref(true)
const user = ref({ name: 'Alice' })
</script>
<!-- ============================================ -->
<!-- PART 9: v-show WITH TRANSITION -->
<!-- ============================================ -->
<template>
<Transition name="fade">
<div v-show="isVisible">Fading content</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(true)
</script>
<!-- ============================================ -->
<!-- PART 10: DECISION IN TEMPLATE COMMENT -->
<!-- ============================================ -->
<template>
<!-- Good: frequently toggled dropdown -->
<Dropdown v-show="menuOpen" />
<!-- Good: rarely toggled error page -->
<ErrorPage v-if="hasError" />
<!-- Bad: v-show on template (does nothing) -->
<template v-show="showGroup">
<p>This won't be toggled</p>
</template>
</template>
The ten parts covered the essentials: basic v-show toggling, computed conditions, direct comparison with v-if, state preservation versus destruction, frequency-based use cases, component usage, transition support, and a visual reminder of the <template> limitation.
Quick Reference
v-show vs v-if Comparison
| Aspect | v-show | v-if |
|---|---|---|
| DOM presence | Always present | Added/removed |
| Mechanism | CSS display toggle | DOM mount/unmount |
| Initial render cost | High (always renders) | Low (lazy) |
| Toggle cost | Very low | High (destroy/rebuild) |
| State preservation | Yes | No |
| Component lifecycle | Preserved | Destroyed/recreated |
v-else support | No | Yes |
<template> support | No | Yes |
| Memory footprint | Always allocated | Only when rendered |
When to Use Which
| Scenario | Recommended Directive |
|---|---|
| Tooltip on hover | v-show |
| Dropdown menu | v-show |
| Modal dialog (single use) | v-if |
| Error page | v-if |
| Frequently toggled panel | v-show |
| Rarely shown admin controls | v-if |
| Form with preserved input | v-show |
| Multi-branch conditional | v-if with v-else-if |
v-show Limitations
| Limitation | Reason |
|---|---|
No v-else | Requires conditional rendering infrastructure |
No <template> | Template has no DOM node for display |
| No lazy rendering | Element always compiled |
| Memory held | Component remains alive |
Best Practices
✅ Do This:
<!-- Use v-show for frequent toggles -->
<div v-show="isHovered">Tooltip</div> <!-- ✅ -->
<!-- Use v-show to preserve form state -->
<form v-show="showForm"> <!-- ✅ -->
<input v-model="data">
</form>
<!-- Use v-show for CSS-dependent visibility -->
<div v-show="menuOpen" class="dropdown"> <!-- ✅ -->
<!-- Use v-show when transitions are desired -->
<Transition name="fade"> <!-- ✅ -->
<div v-show="visible">Content</div>
</Transition>
<!-- Use v-show on real DOM elements -->
<div v-show="showGroup"> <!-- ✅ -->
<p>Group content</p>
</div>
❌ Don’t Do This:
<!-- Don't use v-show with v-else -->
<div v-show="condition">A</div> <!-- ❌ -->
<div v-else>B</div> <!-- ❌ -->
<!-- Don't use v-show on template -->
<template v-show="showGroup"> <!-- ❌ -->
<p>Content</p>
</template>
<!-- Don't use v-show for rarely-seen content -->
<AdminPanel v-show="isAdmin" /> <!-- ❌ (use v-if) -->
<!-- Don't use v-show when state should reset -->
<Form v-show="showForm" /> <!-- ❌ if fresh state wanted -->
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
v-show on <template> does nothing | No DOM node to toggle | Apply v-show to child elements |
| Form warns on submit when hidden | v-if disconnected the form | Use v-show to keep form in DOM |
| Input value persists unexpectedly | v-show preserves state | Use v-if if reset is desired |
| Memory grows with hidden content | v-show keeps components alive | Use v-if for large, rare content |
v-show with v-else ignored | No branching support | Use v-if for conditionals |
| Performance worse than expected | Toggling expensive v-if block | Switch to v-show for frequent toggles |
Real-World Examples
1. Hover Tooltip
<span @mouseenter="show = true" @mouseleave="show = false">
<span v-show="show">Help text</span>
</span>
2. Collapsible Sidebar
<aside v-show="sidebarOpen" class="sidebar">
<nav>...</nav>
</aside>
3. Form with Preserved Input
<form v-show="showForm">
<input v-model="email">
</form>
4. Loading Overlay
<div v-show="isLoading" class="overlay">Loading...</div>
5. Error Message (Frequent Toggle)
<p v-show="validationError" class="error">{{ validationError }}</p>
6. Modal (Single Use)
<Modal v-if="showModal" @close="showModal = false" />
7. Conditional Navigation Item
<RouterLink v-show="user.role === 'admin'" to="/admin">Admin</RouterLink>
8. Tab Content
<div v-show="activeTab === 'profile'">Profile content</div>
<div v-show="activeTab === 'settings'">Settings content</div>
9. Toggle with Transition
<Transition name="slide">
<div v-show="panelOpen">Panel</div>
</Transition>
10. Component Visibility
<UserCard v-show="showCard" :user="user" />
Visual
DOM State: v-show vs v-if
┌─────────────────────────────────────────────────────────────┐
│ CONDITION: TRUE │
│ │
│ v-show: <div>Content</div> │
│ v-if: <div>Content</div> │
│ │
│ Identical DOM output. │
│ │
├─────────────────────────────────────────────────────────────┤
│ CONDITION: FALSE │
│ │
│ v-show: <div style="display: none">Content</div> │
│ (in DOM, hidden by CSS) │
│ │
│ v-if: (nothing in DOM) │
│ (element removed entirely) │
│ │
└─────────────────────────────────────────────────────────────┘
Performance Trade-off
┌─────────────────────────────────────────────────────────────┐
│ INITIAL RENDER COST │
│ │
│ v-show: ████████████████████ (always renders) │
│ v-if: ██ (lazy, skips if false) │
│ │
├─────────────────────────────────────────────────────────────┤
│ TOGGLE COST │
│ │
│ v-show: ██ (CSS property flip) │
│ v-if: ████████████████████ (destroy + rebuild) │
│ │
└─────────────────────────────────────────────────────────────┘
Decision Flow
┌─────────────────────────────────────────────────────────────┐
│ CHOOSING BETWEEN v-show AND v-if │
│ │
│ Does the condition change frequently? │
│ │ │
│ ├── YES ──▶ v-show │
│ │ │
│ └── NO │
│ │ │
│ ├── Does state need to persist? │
│ │ ├── YES ──▶ v-show │
│ │ └── NO ──▶ v-if │
│ │ │
│ └── Do you need v-else branches? │
│ ├── YES ──▶ v-if │
│ └── NO ──▶ v-if (if rarely shown) │
│ │
└─────────────────────────────────────────────────────────────┘
Lifecycle Comparison
┌─────────────────────────────────────────────────────────────┐
│ v-show: COMPONENT STAYS ALIVE │
│ │
│ mount ──▶ visible ──▶ hidden ──▶ visible ──▶ unmount │
│ │
│ Component instance persists through hidden phase. │
│ State, watchers, and event listeners remain active. │
│ │
├─────────────────────────────────────────────────────────────┤
│ v-if: COMPONENT DESTROYED AND RECREATED │
│ │
│ mount ──▶ visible ──▶ DESTROY ──▶ mount ──▶ unmount │
│ │
│ Each toggle triggers full lifecycle: │
│ onUnmounted → onMounted → onBeforeUnmount → etc. │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Directive | v-show toggles visibility via CSS display |
| DOM behavior | Element always present |
| Toggle mechanism | Inline display: none or restore |
| Initial cost | High—always renders |
| Toggle cost | Very low |
| State | Preserved across toggles |
v-else support | No |
<template> support | No |
| Best for | Frequent toggles, state preservation |
| Worst for | Rarely shown content, memory-constrained contexts |
v-if contrast | Removes from DOM, destroys state |
Key takeaways:
v-showtoggles CSS, not DOM. The element remains in the document at all times. Only itsdisplayproperty changes. This is the fundamental distinction fromv-if.- State survives visibility changes. Because the component is never destroyed, form inputs, scroll positions, and component-local state persist when the element is hidden and revealed again.
- Initial cost is paid regardless of condition.
v-showcompiles and renders the element even when the condition is initially false.v-ifis lazy and skips this work entirely. - Toggle cost is nearly free. Flipping a CSS property is orders of magnitude cheaper than destroying and rebuilding a DOM subtree. This is why
v-showis preferred for frequently toggled content. v-showcannot branch. There is nov-elseforv-show. If you need conditional alternatives, you needv-if. Similarly,<template>elements cannot be toggled withv-showbecause they have no DOM node to manipulate.- Memory is the hidden cost. A hidden
v-showcomponent still occupies memory. Its reactive state, watchers, and computed properties remain allocated. For large components that are rarely shown,v-iffrees resources thatv-showwould hold indefinitely. - The decision is about frequency and state. Toggle frequency is the primary signal: frequent toggles favor
v-show; rare toggles favorv-if. State preservation is the secondary signal: if state should persist,v-show; if it should reset,v-if.
Remember: v-show and v-if are not competing implementations of the same feature. They are two different tools that happen to produce similar visible results under different engineering constraints. v-show says “keep this in the DOM, just hide it.” v-if says “this should not exist right now.” The choice between them is a statement about what you expect the condition to do and what you expect the element’s state to do. Get that mental model right, and the directive choice becomes obvious.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!