Skip to main content
Practice Problems

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 DirectivesUse Composables
Direct DOM manipulationReactive state logic
Third-party library integrationData fetching
Low-level event handlingShared business logic
Element-specific behaviorComponent-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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems