Directives are special attributes prefixed with v- that give templates superpowers: conditionally render blocks, loop over lists, bind data to attributes, and wire form inputs to state. Mastering the four core directives in this lesson covers 90% of everyday template work in Vue 3.
v-if renders an element only when its expression is truthy:
<script setup>
import { ref } from 'vue'
const status = ref('loading')
</script>
<template>
<p v-if="status === 'loading'">Loading…</p>
<p v-else-if="status === 'error'">Something went wrong.</p>
<p v-else>Data loaded!</p>
</template>v-else and v-else-if must immediately follow a v-if sibling. To toggle several elements together without adding a wrapper div, put v-if on a <template> tag -- it renders its children with no extra DOM node.
v-show keeps the element in the DOM and toggles display: none. Use v-show for things that toggle frequently (cheap to switch, costly upfront) and v-if for branches that rarely change or are expensive to create.
v-for repeats an element for each item in an array, object, or range:
<script setup>
import { ref } from 'vue'
const todos = ref([
{ id: 1, text: 'Learn directives', done: true },
{ id: 2, text: 'Build an app', done: false }
])
</script>
<template>
<ul>
<li v-for="(todo, index) in todos" :key="todo.id">
{{ index + 1 }}. {{ todo.text }}
</li>
</ul>
</template>Always provide :key with a stable, unique value (an id, not the index). Keys let Vue track each node's identity so reordering, inserting, and deleting items updates the DOM correctly and efficiently. Avoid combining v-if and v-for on the same element; wrap one in a <template> instead, because v-if has higher priority in Vue 3 and cannot see the loop variable.
You can also iterate objects (v-for="(value, key) in user") and ranges (v-for="n in 5").
You met v-bind in the previous lesson; here are the patterns you will use daily:
<a :href="profileUrl" :title="'Visit ' + userName">Profile</a>
<input :placeholder="hint" :maxlength="limit">
<div :class="[baseClass, isActive ? 'on' : 'off']"></div>Binding an object of attributes at once is possible with v-bind="attrsObject", which spreads every key as an attribute.
v-model creates a two-way link between a form input and your state -- typing updates the data, and updating the data changes the input:
<script setup>
import { ref } from 'vue'
const message = ref('')
const agreed = ref(false)
const picked = ref('vue')
</script>
<template>
<input v-model="message" placeholder="Type here">
<p>You typed: {{ message }}</p>
<input type="checkbox" v-model="agreed"> I agree
<select v-model="picked">
<option value="vue">Vue</option>
<option value="react">React</option>
</select>
</template>v-model adapts automatically: text inputs bind value, checkboxes bind checked, selects bind the selected option. Three useful modifiers:
v-model.trim strips whitespace from the ends.v-model.number casts the input to a number.v-model.lazy syncs on change instead of every keystroke.<template>
<input v-model.trim="newTodo" @keyup.enter="addTodo" placeholder="Add a task">
<p v-if="todos.length === 0">Nothing to do 🎉</p>
<ul v-else>
<li v-for="todo in todos" :key="todo.id" :class="{ done: todo.done }">
<input type="checkbox" v-model="todo.done"> {{ todo.text }}
</li>
</ul>
</template>This tiny todo template combines all four directives -- exactly how real Vue apps are built.
v-if/v-else-if/v-else add or remove elements; v-show merely hides them.v-for needs a stable :key; never key by array index if the list reorders.v-if and v-for on the same element in Vue 3.v-model gives two-way binding on inputs, with .trim, .number, and .lazy modifiers.Next up: Handling Events -- listening to clicks, key presses, and more with v-on.