Chapter 3: Vue Directives
Vue Directives are exactly that: special HTML attributes that start with v- (like v-if, v-for, v-model). Vue sees them and takes control — it watches your data and updates the DOM reactively.
This is declarative programming: you describe what should happen, not how to do it step-by-step.
In Vue 3 (2026 standard), directives work the same in Options API and Composition API — they’re part of the template syntax.
General Syntax of a Directive
|
0 1 2 3 4 5 6 |
<tag v-directive:argument.modifier="expression"> |
- v-directive → name (e.g. v-if, v-on)
- :argument → optional (e.g. :class, :click)
- .modifier → optional (e.g. .prevent, .trim)
- “expression” → JavaScript expression (most cases — except a few like v-for)
Shorthands make life easier:
- v-bind:xxx → :xxx
- v-on:xxx → @xxx
- v-slot:xxx → #xxx
Now let’s go through all the important built-in directives one by one, with real examples. I’ll use modern <script setup> style.
1. v-bind (:) — Dynamic attributes & props
Binds an attribute or prop to a JS value.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<template> <img :src="imageUrl" :alt="productName" /> <!-- Class binding (object / array syntax) --> <div :class="{ active: isActive, 'text-red': hasError }"> Status </div> <!-- Style binding --> <div :style="{ color: textColor, fontSize: fontSize + 'px' }"> Styled text </div> <!-- Dynamic attribute name (Vue 3) --> <button :[attrName]="true">Dynamic attr</button> <!-- Same-name shorthand (Vue 3.4+) --> <input :disabled> <!-- equivalent to :disabled="disabled" if you have a 'disabled' ref --> </template> <script setup> import { ref } from 'vue' const imageUrl = ref('https://example.com/logo.png') const productName = ref('Vue Mug') const isActive = ref(true) const hasError = ref(false) const textColor = ref('navy') const fontSize = ref(18) const attrName = ref('data-test') </script> |
Tip: Use :class and :style a lot — they’re super powerful.
2. v-on (@) — Event handling
Listens to events.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<template> <button @click="count++">Count: {{ count }}</button> <!-- With modifier --> <form @submit.prevent="submitForm"> <input type="text" /> <button type="submit">Send</button> </form> <!-- Key modifier --> <input @keyup.enter="search" placeholder="Press Enter" /> <!-- Multiple events with object syntax --> <div v-on="{ mouseenter: showTooltip, mouseleave: hideTooltip }"> Hover me </div> </template> <script setup> const count = ref(0) function submitForm() { alert('Submitted!') } function search() { console.log('Searching...') } function showTooltip() { /* ... */ } function hideTooltip() { /* ... */ } </script> |
Popular modifiers: .prevent, .stop, .once, .self, .passive, .capture, .enter, .esc, .tab, .left, etc.
3. v-model — Two-way data binding (forms magic)
Syncs input with data.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<template> <input v-model="name" placeholder="Your name" /> <p>Hello, {{ name }}!</p> <!-- With modifiers --> <input v-model.trim="message" placeholder="Trimmed" /> <input v-model.number="age" type="number" /> <input v-model.lazy="slowUpdate" placeholder="Updates on blur" /> <!-- Checkbox / radio / select --> <input type="checkbox" v-model="likesVue" /> I like Vue <select v-model="selectedFruit"> <option>Apple</option> <option>Banana</option> </select> </template> <script setup> const name = ref('') const message = ref('') const age = ref(null) const slowUpdate = ref('') const likesVue = ref(true) const selectedFruit = ref('Banana') </script> |
v-model on components → custom inputs become easy (more later).
4. v-if / v-else-if / v-else — Conditional rendering (removes from DOM)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<template> <div v-if="userRole === 'admin'"> Admin Dashboard </div> <div v-else-if="userRole === 'editor'"> Editor Panel </div> <div v-else> Guest View </div> <!-- With <template> (no extra wrapper) --> <template v-if="showModal"> <div class="modal">Modal content</div> <div class="backdrop"></div> </template> </template> <script setup> const userRole = ref('guest') const showModal = ref(false) </script> |
Note: v-if is lazy — content isn’t even compiled/rendered until true. Good for performance.
5. v-show — Conditional visibility (stays in DOM, just display: none)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
<template> <p v-show="isVisible">You can see me, but I'm just hidden</p> <button @click="isVisible = !isVisible">Toggle</button> </template> <script setup> const isVisible = ref(true) </script> |
v-show vs v-if:
- Use v-show when toggling often (cheaper, keeps state)
- Use v-if when condition rarely changes or for initial load performance
6. v-for — List rendering (the most used after v-bind/v-on)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<template> <ul> <li v-for="todo in todos" :key="todo.id"> {{ todo.text }} <button @click="removeTodo(todo.id)">×</button> </li> </ul> <!-- With index --> <ol> <li v-for="(color, index) in colors" :key="index"> {{ index + 1 }}. {{ color }} </li> </ol> <!-- Object loop --> <dl v-for="(value, key) in person" :key="key"> <dt>{{ key }}</dt> <dd>{{ value }}</dd> </dl> </template> <script setup> const todos = ref([ { id: 1, text: 'Learn directives' }, { id: 2, text: 'Build todo app' } ]) const colors = ref(['Red', 'Green', 'Blue']) const person = reactive({ name: 'Rahul', city: 'Hyderabad', age: 28 }) function removeTodo(id) { todos.value = todos.value.filter(t => t.id !== id) } </script> |
Rule: Always add :key (unique identifier) — helps Vue track items efficiently when list changes.
7. v-text & v-html — Text & raw HTML
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<template> <!-- v-text = safer than {{ }} when you don't want mustache parsing --> <p v-text="rawText"></p> <!-- v-html = DANGEROUS – only trusted content! (XSS risk) --> <div v-html="userBioHtml"></div> </template> <script setup> const rawText = ref('No <b>HTML</b> here') const userBioHtml = ref('<b>Bold</b> from server <script>alert("bad")</script>') </script> |
Never use v-html with user input unless sanitized!
8. Others worth knowing (less common but useful)
- v-once → render once, never update again <span v-once>{{ willNeverChange }}</span>
- v-memo (Vue 3.2+) → memoize subtree if deps unchanged <div v-memo=”[price, currency]”>Expensive block</div>
- v-pre → skip compilation (show raw {{}}) <code v-pre>{{ raw mustache }}</code>
- v-cloak → hide mustache flash in no-build setups CSS: [v-cloak] { display: none }
- v-slot (#) → for scoped slots (we’ll cover in components lesson)
Quick Summary Table
| Directive | Purpose | Shorthand | Common Use Case |
|---|---|---|---|
| v-bind | Bind attr/prop | : | :class, :style, :src, props |
| v-on | Event listener | @ | @click, @submit.prevent |
| v-model | Two-way form binding | — | Inputs, checkboxes, custom comp |
| v-if / v-else | Conditional render (remove/add) | — | Auth checks, tabs |
| v-show | Conditional visibility | — | Frequent toggles ( accordions ) |
| v-for | Loop over list/object | — | Lists, tables, options |
| v-html | Inject raw HTML | — | Trusted markdown/server content |
| v-text | Set textContent | — | Plain text output |
| v-once | Static render once | — | Prices, timestamps |
| v-memo | Conditional memoization | — | Performance in large lists |
Pro Tip from 2026
In modern Vue 3 projects (Vite + <script setup>), you use directives a lot in templates — but heavy logic moves to computed, watch, composables.
Practice: Build a small app with all these — a filtered todo list with add/remove, search input (v-model), status toggle (v-show), role-based UI (v-if), etc.
Any directive confusing? Want a full mini-project example combining 5–6 of them? Or how to make a custom directive (like v-focus, v-click-outside)?
Just say — we’ll dive deeper 🚀
Happy Vue-directive-ing from Hyderabad! 🌶️
