Custom directives in Vue.js
What are Custom Directives?
Custom directives extend Vue's template syntax with reusable DOM manipulation logic. While components are the main building blocks, directives are useful for low-level DOM access that doesn't fit the component model.
Basic Directive
vue
<!-- Auto-focus input on mount -->
<script setup>
const vFocus = {
mounted(el: HTMLElement) {
el.focus()
}
}
</script>
<template>
<input v-focus />
</template>Directive Lifecycle Hooks
typescript
const myDirective = {
// Called before element is mounted
created(el, binding, vnode) {},
// Called when element is inserted into DOM
mounted(el, binding, vnode) {},
// Called before parent component updates
beforeUpdate(el, binding, vnode, prevVnode) {},
// Called after parent component updates
updated(el, binding, vnode, prevVnode) {},
// Called before element is unmounted
beforeUnmount(el, binding, vnode) {},
// Called when element is unmounted
unmounted(el, binding, vnode) {},
}The binding Object
vue
<div v-my-directive:arg.mod1.mod2="value">
<!-- binding contains: -->
<!-- {
value: value, // Current value
oldValue: ..., // Previous value (in updated)
arg: 'arg', // Argument after colon
modifiers: { mod1: true, mod2: true },
instance: ..., // Component instance
dir: ..., // Directive definition
} -->Practical Examples
Click Outside
typescript
// directives/clickOutside.ts
export const vClickOutside = {
mounted(el: HTMLElement, binding: { value: () => void }) {
el._clickOutsideHandler = (event: MouseEvent) => {
if (!el.contains(event.target as Node)) {
binding.value()
}
}
document.addEventListener('click', el._clickOutsideHandler)
},
unmounted(el: HTMLElement) {
document.removeEventListener('click', el._clickOutsideHandler)
},
}vue
<template>
<div v-click-outside="closeDropdown" class="dropdown">
<!-- Dropdown content -->
</div>
</template>Intersection Observer (Lazy Load)
typescript
export const vLazyLoad = {
mounted(el: HTMLImageElement, binding: { value: string }) {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
el.src = binding.value
observer.disconnect()
}
})
observer.observe(el)
},
}vue
<img v-lazy-load="imageUrl" />Tooltip
typescript
export const vTooltip = {
mounted(el: HTMLElement, binding: { value: string }) {
el.title = binding.value
el.style.cursor = 'help'
},
updated(el: HTMLElement, binding: { value: string }) {
el.title = binding.value
},
}Global Registration
typescript
// main.ts
import { createApp } from 'vue'
import { vClickOutside } from './directives/clickOutside'
const app = createApp(App)
app.directive('click-outside', vClickOutside)
app.mount('#app')When to Use Directives vs Composables
| Use Directives | Use Composables |
|---|---|
| Direct DOM manipulation | Reactive state logic |
| Third-party library integration | Data fetching |
| Low-level event handling | Shared business logic |
| Element-specific behavior | Component-agnostic logic |
Important:
Custom directives are for low-level DOM manipulation that doesn't fit in components or composables. Common use cases include click-outside detection, intersection observer, tooltips, and focus management. For everything else, prefer composables (reusable functions) as they're more flexible and testable.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.