Custom Directives
Custom directives provide low-level DOM access for reusable behavior.
When to Use
Use custom directives when:
- You need direct DOM manipulation
- The behavior can't be achieved with components or composables
- You need to apply behavior to native elements
Basic Example
<script setup lang="ts">
// v-focus directive
const vFocus = {
mounted: (el: HTMLElement) => el.focus()
}
</script>
<template>
<input v-focus />
</template>Directive Hooks
const myDirective = {
// Before element attributes/listeners are applied
created(el, binding, vnode) {},
// Before element is inserted into DOM
beforeMount(el, binding, vnode) {},
// After element and children are mounted
mounted(el, binding, vnode) {},
// Before parent component updates
beforeUpdate(el, binding, vnode, prevVnode) {},
// After parent component updates
updated(el, binding, vnode, prevVnode) {},
// Before parent component unmounts
beforeUnmount(el, binding, vnode) {},
// After parent component unmounts
unmounted(el, binding, vnode) {}
}Hook Arguments
interface DirectiveBinding<T = any> {
value: T // v-my-dir="value"
oldValue: T // Previous value (beforeUpdate/updated only)
arg?: string // v-my-dir:arg
modifiers: Record<string, boolean> // v-my-dir.foo.bar → { foo: true, bar: true }
instance: ComponentPublicInstance // Component using the directive
dir: ObjectDirective // Directive definition object
}Example usage:
<div v-example:foo.bar="baz">// binding object:
{
arg: 'foo',
modifiers: { bar: true },
value: /* value of baz */,
oldValue: /* previous value */
}Function Shorthand
When you only need mounted and updated with same behavior:
// Full form
const vColor = {
mounted(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
el.style.color = binding.value
}
}
// Shorthand (same behavior)
const vColor = (el: HTMLElement, binding: DirectiveBinding<string>) => {
el.style.color = binding.value
}Global Registration
// main.ts
const app = createApp(App)
app.directive('focus', {
mounted: (el) => el.focus()
})
// Shorthand
app.directive('color', (el, binding) => {
el.style.color = binding.value
})Object Literals
Pass multiple values:
<div v-demo="{ color: 'white', text: 'hello' }">const vDemo = (el: HTMLElement, binding: DirectiveBinding<{ color: string; text: string }>) => {
console.log(binding.value.color) // 'white'
console.log(binding.value.text) // 'hello'
}Dynamic Arguments
<div v-my-directive:[dynamicArg]="value">Practical Examples
v-click-outside
const vClickOutside = {
mounted(el: HTMLElement, binding: DirectiveBinding<() => void>) {
el._clickOutside = (event: MouseEvent) => {
if (!el.contains(event.target as Node)) {
binding.value()
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el: HTMLElement) {
document.removeEventListener('click', el._clickOutside)
}
}v-tooltip
const vTooltip = {
mounted(el: HTMLElement, binding: DirectiveBinding<string>) {
el.setAttribute('title', binding.value)
},
updated(el: HTMLElement, binding: DirectiveBinding<string>) {
el.setAttribute('title', binding.value)
}
}v-permission
const vPermission = {
mounted(el: HTMLElement, binding: DirectiveBinding<string>) {
if (!hasPermission(binding.value)) {
el.parentNode?.removeChild(el)
}
}
}TypeScript: Global Directives
// directives/highlight.ts
import type { Directive } from 'vue'
export type HighlightDirective = Directive<HTMLElement, string>
declare module 'vue' {
export interface ComponentCustomProperties {
vHighlight: HighlightDirective
}
}
export default {
mounted: (el, binding) => {
el.style.backgroundColor = binding.value
}
} satisfies HighlightDirectiveUsage on Components
⚠️ Not recommended - directives apply to root element, which can be unpredictable with multi-root components.
<!-- Applies to MyComponent's root element -->
<MyComponent v-my-directive /><!-- Source references:
- https://vuejs.org/guide/reusability/custom-directives.html
-->