Directives, lists and conditional rendering
v-if versus v-show, v-for with stable keys, binding classes and styles, and the raw-HTML directive that causes most Vue security bugs.
v-if, v-else and v-show
<p v-if="status === 'loading'">Loading...</p>
<p v-else-if="status === 'error'">Something went wrong.</p>
<p v-else>{{ data.title }}</p>
<!-- v-show always renders the element and toggles display instead -->
<div v-show="open" class="panel">Hidden but present in the DOM</div>
<!-- v-if creates and destroys, so the child's local state is reset -->
<UserForm v-if="editing" :user="user" />v-ifis lazy: the branch is not rendered until the condition first becomes true, and it creates and destroys the elements on every toggle.v-showalways mounts and pays one render up front; use it for something switched often, such as a dropdown or a tab panel.v-elseandv-else-ifmust be immediate siblings of thev-ifelement. A comment or another element in between breaks the chain.- The deciding question is usually state:
v-showpreserves a half-typed form,v-ifthrows it away. Choose deliberately. - Keep the condition readable by moving it into a
computed; a five-clause comparison in the template cannot be unit tested.
Lists and keys
import { ref, computed } from 'vue'
const items = ref([
{ id: 'a1', title: 'Install Vite', done: false },
{ id: 'a2', title: 'Write a component', done: true }
])
const visible = computed(() => items.value.filter(i => !i.done))<ul>
<li v-for="(item, index) in visible" :key="item.id">
{{ index + 1 }}. {{ item.title }}
</li>
</ul>
<!-- an object iterates as (value, key) -->
<dl>
<template v-for="(value, key) in settings" :key="key">
<dt>{{ key }}</dt><dd>{{ value }}</dd>
</template>
</dl>:keymust be a stable identity from the data — an id or a slug. Using the loop index is the single most common list bug.- With an index key Vue reuses the DOM node at that position and patches its contents, so input values, transitions and child state end up on the wrong row after a sort or a delete.
- Never combine
v-forandv-ifon the same element: in Vue 3v-ifhas higher priority, so the condition cannot see the loop variable. Filter with a computed, or putv-ifon the inner element. push,spliceand index assignment on a reactive array are all tracked. Replacing the whole array on arefstill needs.value.- Sorting and filtering should produce a new array in a computed rather than mutating the source list, so the original order is never lost.
Classes, styles and raw HTML
<div :class="['card', { 'card-open': open, 'card-muted': !hasData }]"></div>
<div :class="open ? 'card-open' : 'card-idle'"></div>
<!-- static class and :class are merged, not replaced -->
<button class="btn" :class="kind" @click="save">Save</button>
<span :style="{ color: tone, fontSize: size + 'px' }"></span>
<div v-html="trustedHtml"></div>- In an object passed to
:class, each key is included when its value is truthy; an array merges entries and allows nested objects and ternaries. - A plain
classattribute is kept and merged with the bound value, so base styling does not need to be repeated in the expression. :styletakes camelCase property names and accepts an array to merge several objects; reach for a class instead whenever the style is not data-driven.- Style values should come from a number or a fixed allowlist. Interpolating a user string into
:styleis a CSS injection surface. - Bind
:keyand other attributes with:when they must be dynamic; a plain attribute is a static string.
⚠️
v-html inserts a string as real HTML with no sanitisation, so any value a user can influence is an XSS hole. Use text interpolation for anything untrusted, and if you genuinely need rich HTML, sanitise it with a maintained library before it reaches the template.FAQ
Why does my list show the wrong values after sorting?
The key is the loop index. Vue patches the element at each position rather than moving the elements, so the content and any DOM state stay attached to the wrong row. Use the item's own id as the key.
Should I prefer v-if or v-show?
Use
v-if when the branch is expensive, rarely shown, or must be fully reset each time. Use v-show when the element is toggled often and you want to keep its state and DOM node.Related
Forms and user input Setting up a Vue project with Vite
Last refreshed 2026-09-18.