Vue custom directives let you reuse low-level DOM behavior across elements when declarative bindings are not enough. Examples include focusing an input on mount, detecting clicks outside a dropdown, or attaching scroll observers.
In <script setup>, any camelCase variable starting with lowercase v (such as vFocus) registers automatically as a directive (v-focus). You can also register directives globally in main.js with app.directive().
flowchart LR
Directive["v-focus in Template"] --> Hook["mounted(el, binding)"]
Hook --> DOM["el.focus() Native DOM Call"]
Each custom directive object can define hooks matching Vue's component lifecycle:
created(el, binding, vnode): Runs before the element's attributes or event listeners apply.mounted(el, binding, vnode): Runs once the element is inserted into the DOM, making it safe to focus or measure.updated(el, binding, vnode): Runs after the parent component and all its children update.unmounted(el, binding): Runs when the element leaves the DOM so you can remove global event listeners.This component defines two local directives inside <script setup>: vFocus for auto-focusing inputs, and vHighlight for reading reactive values from binding.value.
<script setup>
import { ref } from 'vue';
const vFocus = {
mounted(el) {
el.focus();
}
};
const vHighlight = {
mounted(el, binding) {
el.style.backgroundColor = binding.value || '#fef08a';
},
updated(el, binding) {
el.style.backgroundColor = binding.value || '#fef08a';
}
};
const highlightColor = ref('#bfdbfe');
</script>
<template>
<div class="directive-demo">
<label>
Auto-focused Input:
<input v-focus type="text" placeholder="Focused on mount" />
</label>
<p v-highlight="highlightColor">
This paragraph uses a custom directive for background color.
</p>
<button @click="highlightColor = '#bbf7d0'">Switch to Green</button>
</div>
</template>
mounted Without updated: If your directive reads a reactive binding.value, forgetting the updated hook stops the DOM from updating when that value changes.unmounted: If mounted attaches window.addEventListener, always call window.removeEventListener in unmounted and store the handler on el or a WeakMap.binding object contain?binding includes value (the passed value), oldValue (previous value in updated), arg (such as 'foo' in v-my:foo), and modifiers (such as { prevent: true }).
If you only need the same logic for both mounted and updated, pass a single function instead of an object: const vHighlight = (el, binding) => { ... }.
Not recommended. In Vue 3, components can have multiple root nodes, so directives placed on component tags behave unpredictably.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With
You've completed this section! Take a quick 5-question quiz to check your understanding.