Read More on Telerik Blogs
February 04, 2026 Web, Vue
Get A Free Trial

New to Vue 3? This guide breaks down everything you need to know about directives, from dynamic bindings to conditional rendering and custom behaviors, with examples you can use right away.

When first working with Vue, you’ll quickly notice how efficiently you can build dynamic applications, thanks in large part to one of its core features: directives. These v- prefixed attributes drive Vue’s way of syncing the DOM with your data, handling conditional rendering, binding and event listening seamlessly.

Directives define how Vue extends HTML with dynamic behaviors, forming the backbone of its reactivity. Understanding how directives observe and react to data changes is key to mastering Vue’s reactive system and creating dynamic interfaces.

In this guide, we’ll break down how Vue 3 directives work, explore the core built-in directives you’ll use every day, learn how to create them and look at some best practices to keep your templates clean and efficient. Whether you’re just starting out or brushing up on your Vue fundamentals, this post will give you a solid, practical understanding of how directives fit into Vue’s reactive world.

What Are Directives in Vue ?

At their core, directives are special HTML attributes that start with v- and apply reactive behavior to a DOM element.

Think of them as Vue’s way of giving HTML a “brain.” Instead of writing manual DOM logic, you describe what you want to happen, and Vue handles the how.

Here’s a simple example:

<p v-if="isVisible">Hello Vue!</p>

When isVisible is true, Vue renders the paragraph. When it’s false, Vue removes it from the DOM. No manual toggling, no query selectors, no manual updates. Just reactive behavior, declaratively defined.

The Anatomy of a Directive

A directive looks like this:

v-directiveName:argument.modifier="expression"

Let’s break that down:

  • Directive name: E.g, v-if, v-bind, v-model
  • Argument (optional): Targets something specific, like v-bind:href
  • Modifier (optional): Tweaks behavior, like .prevent or .once
  • Expression (optional): The data or logic it depends on

Here’s a quick example showing all parts in action:

<a v-bind:href="url" @click.prevent="trackClick">Visit</a>

Here:

  • v-bind binds the href attributes to your component’s url.
  • @click.prevent is shorthand for v-on:click.prevent, meaning “listen for a click and prevent the default browser behavior.”

Vue directives combine clean syntax with smart reactivity, and once you get used to them, you’ll rarely want to go back to manual DOM manipulation.

Built-in Directives You’ll Use Every Day

Vue ships with a handful of built-in directives that cover almost every common DOM scenario. Let’s go through the most important ones.

v-bind: Dynamic Attribute Binding

v-bind connects your templates to your component’s reactive data. You can bind any attribute—src, href, class, style and more to a reactive value.

<img v-bind:src="imageUrl" />

That’s the long form. The shorthand is much more common:

<img :src="imageUrl" />

You can even bind multiple attributes using an object:

<div v-bind="{ id: someId, class: someClass }"></div>

v-model: Two-Way Data Binding

v-model is one of Vue’s most magical features. It keeps your form inputs in sync with component data—automatically.

<input v-model="message" placeholder="Type something..." />
<p>{{ message }}</p>

Every time you type, the data updates. Every time the data changes, the input updates. No event listeners, no manual syncing. Just pure reactivity.

You can even use modifiers like .lazy, .number and .trim:

<input v-model.lazy="username" />

This tells Vue to update the data after the input loses focus, rather than on every keystroke.

v-if, v-else-if, v-else: Conditional Rendering

These control whether an element appears in the DOM based on a condition.

<p v-if="user">Welcome back, {{ user.name }}!</p>
<p v-else>Login to continue</p>

When the condition is false, the element is literally removed from the DOM, not just hidden. That’s great for performance when toggling expensive UI sections.

v-show: Toggle Visibility

v-show is similar to v-if, but instead of removing elements from the DOM, it simply toggles its display style:

<p v-show="isVisible">This element is still in the DOM</p>

Use v-show when you need frequent toggling and don’t want to destroy/recreate elements each time.

Quick rule of thumb:

  • Use v-if when toggling rarely (it’s more expensive to re-create elements).
  • Use v-show when toggling frequently (it’s faster to show/hide).

v-for: List Rendering

v-for lets you loop through arrays (or objects) to render lists.

<ul>
  <li v-for="(todo, index) in todos" :key="index">
    {{ todo.text }}
  </li>
</ul>

Always remember to include a unique :key. It helps Vue efficiently track changes and re-render only what’s necessary.

v-on: Event Handling

This one handles DOM events. It’s used to listen for and react to user actions.

<ul>
  <li v-for="(todo, index) in todos" :key="index">
    {{ todo.text }}
  </li>
</ul>

Or, with shorthand:

<button @click="count++">Clicked {{ count }} times</button>

You can chain event modifiers to control behavior:

<form @submit.prevent="submitForm">
  <input v-model="name" />
</form>

The .prevent modifier cancels the default browser action, just like event.preventDefault() would.

v-html: Rendering Raw HTML

This directive lets you render HTML content directly into the DOM.

<div v-html="rawHtml"></div>

Use it carefully, because if rawHtml contains user-generated content, it could expose you to XSS attacks. For most cases, stick with {{ }} interpolation instead.

Modifiers and Arguments in Action

Modifiers and arguments are what make directives flexible.

<input v-model.trim="username" />
<button @click.once="handleClick">Click me once</button>
  • .trim cleans whitespace automatically.
  • .once means the event only fires once.

These small touches make Vue’s syntax incredibly expressive and readable.

Creating Custom Directives

Sometimes, the built-ins don’t cover what you need. Maybe you need to auto-focus an input, trigger animations on scroll or integrate with a third-party library. That’s when custom directives come in.

Here’s a simple example:

app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

Usage:

<input v-focus />

Boom—instant auto-focus whenever the element mounts.

Custom directives have their own lifecycle hooks:

  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted

Each hook gives you access to the element and the binding data, so you can respond to changes however you like.

Best Practices for Using Directives

Here are a few best practices for using directives:

  • Keep logic inside directives simple. For complex logic, use computed properties or watchers.
  • Avoid stacking too many directives on one element—it becomes harder to read.
  • Use v-if and v-show appropriately based on how often the element toggles.
  • Create custom directives only when a pattern truly repeats across components.

Think of directives as glue between Vue’s reactivity and the DOM, not as replacements for component logic.

Common Pitfalls to Avoid

Here are some common pitfalls to avoid when using directives:

  • Using v-html with untrusted content (security risk)
  • Forgetting to add a key in v-for loops
  • Combining v-if and v-for on the same element (always separate them)
  • Overcomplicating templates with too much logic inside directives.

Directives are powerful, but they’re meant to stay declarative. The heavy lifting should still live in your component logic.

Putting It All Together—A Mini Example

It’s one thing to read about directives, but seeing them in action ties everything together.

Below is a small Vue Todo app that shows how multiple directives work in harmony—from conditional rendering to event handling and even custom directives.

You can try this example live in CodeSandbox or StackBlitz, or drop it into your local Vue project to see it run.

You can try this example live in CodeSandbox or StackBlitz, or drop it into your local Vue project to see it run.

<template>
  <div class="todo-app">
    <h2>My Todo List</h2>

    <!-- Input field with v-model and a custom v-focus directive -->
    <input
      v-model.trim="newTodo"
      v-focus
      placeholder="Add a new todo"
      @keyup.enter="addTodo"
    />

    <!-- Conditional rendering using v-if / v-else -->
    <p v-if="todos.length === 0">No todos yet. Add one above 👆</p>
    <ul v-else>
      <!-- Loop through todos using v-for -->
      <li
        v-for="(todo, index) in todos"
        :key="index"
        :class="{ done: todo.done }"
      >
        <!-- Toggling state with v-on -->
        <span @click="toggleDone(todo)">
          {{ todo.text }}
        </span>

        <!-- Delete button -->
        <button @click.prevent="removeTodo(index)">x</button>
      </li>
    </ul>

    <!-- Show summary using v-show -->
    <p v-show="todos.length > 0">
      {{ remaining }} remaining
    </p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// Custom directive: auto-focus input on mount
const vFocus = {
  mounted(el) {
    el.focus()
  },
}

const todos = ref([])
const newTodo = ref('')

const addTodo = () => {
  if (newTodo.value.trim()) {
    todos.value.push({ text: newTodo.value, done: false })
    newTodo.value = ''
  }
}

const removeTodo = (index) => {
  todos.value.splice(index, 1)
}

const toggleDone = (todo) => {
  todo.done = !todo.done
}

const remaining = computed(() => todos.value.filter((t) => !t.done).length)
</script>

<style scoped>
.todo-app {
  max-width: 400px;
  margin: 2rem auto;
  text-align: left;
  font-family: system-ui, sans-serif;
}
.done {
  text-decoration: line-through;
  color: gray;
}
button {
  margin-left: 0.5rem;
  background: none;
  border: none;
  color: crimson;
  cursor: pointer;
}
button:hover {
  color: red;
}
</style>

What’s Happening Here

This small example brings together almost every directive we’ve talked about:

  • v-model keeps the input field and data in sync.
  • v-focus (a custom directive) automatically focuses the input when mounted.
  • v-if / v-else handle conditional rendering for the “todo yet” message.
  • v-for loops through the list of todos and binds a unique :key.
  • @click (shorthand for v-on:click) handles toggling and removing items.
  • v-show conditionally shows the “remaining” count without removing it from the DOM.

This little component shows how directives make your templates reactive, declarative and readable. No manual DOM code, no boilerplate event logic, just Vue doing what it does best.

Wrapping It Up

Directives are one of those features that make Vue feel effortless once you understand them. Instead of micromanaging the DOM, you describe what you want to happen, and Vue handles the rest. From dynamic bindings to conditional rendering and custom behaviors, a directive is the bridge between your data and your UI.

If you’ve followed along up to this point, you’ve already touched nearly every directive you’ll use in day-to-day Vue work, and even built your first custom one. That’s a solid step toward thinking declaratively and writing cleaner, more maintainable Vue components.

In our previous post, we went a level deeper and looked at Vue’s lifecycle hooks, the what happens inside a component. Understanding the lifecycle will help you know exactly when to run logic, register listeners or clean things up as your components mount and unmount.

​If you’ve been enjoying this series, stick around, we’re just getting started. Follow along for more Vue Basics guides where we’ll continue breaking down the framework piece by piece, in a way that feels practical and developer-friendly.


About the Author

David Adeneye Abiodun

David Adeneye is a software developer and a technical writer passionate about making the web accessible for everyone. When he is not writing code or creating technical content, he spends time researching about how to design and develop good software products.

Related Posts