Custom Directives

Introduction

In Vue, custom directives allow you to encapsulate low-level DOM manipulations and reuse them across your application. While components are the main building blocks for UI, and composables are for stateful logic, custom directives are useful for situations where direct DOM manipulation is necessary.

A custom directive is defined as an object containing lifecycle hooks similar to those of a component. These hooks receive the element the directive is bound to. Below is an example of a directive that focuses an input when the element is inserted into the DOM by Vue:

<script setup>
// Enables v-focus in templates
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>

This input should be auto-focused when the component is mounted. In <script setup>, any camelCase variable that starts with the v prefix can be used as a custom directive. In this example, vFocus can be used in the template as v-focus.

If not using <script setup>, custom directives can be registered using the directives option:

export default {
  setup() {
    // ...
  },
  directives: {
    // Enables v-focus in template
    focus: {
      // ...
    }
  }
}

It's also common to globally register custom directives at the app level:

const app = createApp({})

// Make v-focus usable in all components
app.directive('focus', {
  // ...
})

TIP: Custom directives should be used judiciously, and preference should be given to declarative templating using built-in directives like v-bind when possible, as they are more efficient and friendly for server-side rendering.

Directive Hooks

A directive definition object can provide several hook functions, all of which are optional:

const myDirective = {
  created(el, binding, vnode, prevVnode) {
    // Called before bound element's attributes
    // or event listeners are applied
  },
  beforeMount(el, binding, vnode, prevVnode) {
    // Called right before the element is inserted into the DOM.
  },
  mounted(el, binding, vnode, prevVnode) {
    // Called when the bound element's parent component
    // and all its children are mounted.
  },
  beforeUpdate(el, binding, vnode, prevVnode) {
    // Called before the parent component is updated
  },
  updated(el, binding, vnode, prevVnode) {
    // Called after the parent component and
    // all of its children have updated
  },
  beforeUnmount(el, binding, vnode, prevVnode) {
    // Called before the parent component is unmounted
  },
  unmounted(el, binding, vnode, prevVnode) {
    // Called when the parent component is unmounted
  }
}

Hook Arguments: Directive hooks are passed these arguments:

  • el: The element the directive is bound to, used for direct DOM manipulation.

  • binding: An object containing various properties like value, oldValue, arg, modifiers, instance, dir, vnode, and prevNode. These provide information about how the directive is used in the template.

  • vnode: The underlying VNode representing the bound element.

  • prevNode: The VNode representing the bound element from the previous render. Only available in the beforeUpdate and updated hooks.

Note: Custom directives should treat these arguments as read-only and avoid modifying them.

Function Shorthand

If a custom directive has the same behavior for mounted and updated, with no need for the other hooks, you can define the directive as a function:

<template>
  <div v-color="color"></div>
</template>
app.directive('color', (el, binding) => {
  // This will be called for both mounted and updated
  el.style.color = binding.value;
});

Last updated