Directives

A v- prefixed attribute is an instruction to Vue: its name, argument, modifiers and value, and the ones you use daily.

Syntaxv-name:argument.modifier="expression"

A directive is an attribute whose name starts with v-. The browser never sees it. Vue's compiler reads it and turns it into code that updates that element: v-if adds and removes the element, v-for repeats it, v-bind sets an attribute, v-on attaches a DOM event listener.

Anatomy

The full form is v-name:argument.modifier="expression".

  • The name picks the directive.
  • The argument, after the colon, says what it acts on: the attribute for v-bind, the event for v-on. Some directives take none.
  • Modifiers, after a dot, change the behaviour. @submit.prevent calls preventDefault() for you.
  • The value is one JavaScript expression, under the same rules as interpolation.

Three directives have a shorthand, and each pair compiles to exactly the same thing: v-bind:href is :href, v-on:click is @click, v-slot:body is #body.

An argument can be computed at runtime: :[key]="value" takes the attribute name from key. It must evaluate to a string, or to null to drop the binding.

The daily set

v-bind, v-on, v-if with v-else-if and v-else, v-for, v-show, v-model and v-slot. Occasionally useful: v-html, v-text, v-once, and v-pre to leave a block uncompiled.

v-else and v-else-if must sit on the element immediately after the v-if, with nothing between them.

Example

Example · vue
<!-- PackageSearch.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const query = ref('')
const packages = ['vue', 'vite', 'pinia', 'vitest']

function onInput(event: Event): void {
  query.value = (event.target as HTMLInputElement).value
}

function onSubmit(): void {
  query.value = query.value.trim()
}
</script>

<template>
  <form class="search" @submit.prevent="onSubmit">
    <label for="package-query">Package</label>
    <!-- v-bind:value and :value are the same binding -->
    <input id="package-query" v-bind:value="query" @input="onInput" />
    <button type="submit" :disabled="query.length === 0">Trim</button>

    <p v-if="query.length === 0">Type to filter the list.</p>
    <ul v-else>
      <template v-for="name in packages" :key="name">
        <li v-if="name.includes(query)">{{ name }}</li>
      </template>
    </ul>
  </form>
</template>

<style scoped>
.search {
  display: grid;
  gap: 0.5rem;
  max-width: 20rem;
}
</style>

When to use it

  • Read a template written by someone else and tell which attributes are Vue's and which reach the browser.
  • Prevent a form's default submit without writing preventDefault in your handler.
  • Set an attribute whose name is decided by a prop.
  • Print Vue template syntax inside documentation without it being compiled.

More examples

Longhand and shorthand, side by side

Each pair here is one directive written two ways; the compiler produces identical code. Teams normally write the short form, but library documentation and older code use the long one, so you need to read both. Modifiers can be chained, and the order matters because they wrap the handler in sequence: .once.stop fires at most once and stops propagation on that firing.

Example · vue
<!-- ShorthandTour.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const url = ref('https://vuejs.org')
const label = ref('Open the guide')

function track(): void {
  label.value = 'Opened'
}
</script>

<template>
  <!-- attribute binding -->
  <a v-bind:href="url">{{ label }}</a>
  <a :href="url">{{ label }}</a>

  <!-- event listener -->
  <button type="button" v-on:click="track">Track</button>
  <button type="button" @click="track">Track</button>

  <!-- modifiers stack, in the order you write them -->
  <button type="button" @click.once.stop="track">Track once</button>
</template>

A dynamic argument

Square brackets around the argument make it an expression instead of a literal name, for both v-bind and v-on. Use it when the attribute or event name comes from data, such as a design-system component that is handed the attribute to set. The expression must produce a string or null; in a template written directly in an HTML page rather than in a .vue file, the browser lowercases attribute names, so keep the variable name lowercase and free of spaces and quotes.

Example · vue
<!-- MediaFigure.vue -->
<script setup lang="ts">
const props = defineProps<{
  source: string
  kind: 'img' | 'iframe'
}>()

// the attribute name itself is decided at runtime
const sourceAttribute = props.kind === 'img' ? 'src' : 'data-src'
</script>

<template>
  <figure>
    <img v-if="props.kind === 'img'" :[sourceAttribute]="props.source" alt="" />
    <div v-else :[sourceAttribute]="props.source" class="deferred"></div>
  </figure>
</template>

<style scoped>
.deferred {
  min-height: 10rem;
  background: #f1f5f9;
}
</style>

v-once and v-pre

v-once renders a subtree a single time and then skips it on every later update, which is worth it for large static blocks that happen to read a value once. v-pre is the escape hatch for showing Vue syntax itself: without it, the mustaches in that code sample would be compiled and the reader would see the value, or a compile error. Both take no argument and no value.

Example · vue
<!-- SyntaxHelp.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const renderedAt = ref('build time')
</script>

<template>
  <!-- rendered once, then never updated again -->
  <p v-once>Generated: {{ renderedAt }}</p>

  <!-- v-pre: the compiler skips this subtree, braces stay as text -->
  <pre v-pre>Use {{ value }} to print a value.</pre>
</template>

Discussion

  • Be the first to comment on this lesson.