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-if is lazy: the branch is not rendered until the condition first becomes true, and it creates and destroys the elements on every toggle.
  • v-show always mounts and pays one render up front; use it for something switched often, such as a dropdown or a tab panel.
  • v-else and v-else-if must be immediate siblings of the v-if element. A comment or another element in between breaks the chain.
  • The deciding question is usually state: v-show preserves a half-typed form, v-if throws 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>
  • :key must 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-for and v-if on the same element: in Vue 3 v-if has higher priority, so the condition cannot see the loop variable. Filter with a computed, or put v-if on the inner element.
  • push, splice and index assignment on a reactive array are all tracked. Replacing the whole array on a ref still 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 class attribute is kept and merged with the bound value, so base styling does not need to be repeated in the expression.
  • :style takes 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 :style is a CSS injection surface.
  • Bind :key and 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.

Forms and user input Setting up a Vue project with Vite

Last refreshed 2026-09-18.