# Custom Directives

# Intro

In addition to the default set of directives shipped in core (like v-model or v-show), Vue also allows you to register your own custom directives. Note that in Vue, the primary form of code reuse and abstraction is components - however, there may be cases where you need some low-level DOM access on plain elements, and this is where custom directives would still be useful. An example would be focusing on an input element, like this one:

See the Pen Custom directives: basic example by Vue (@Vue) on CodePen.

When the page loads, that element gains focus (note: autofocus doesn't work on mobile Safari). In fact, if you haven't clicked on anything else since visiting this page, the input above should be focused now. Also, you can click on the Rerun button and input will be focused.

Now let's build the directive that accomplishes this:

const app = Vue.createApp({})
// Register a global custom directive called `v-focus`
app.directive('focus', {
  // When the bound element is mounted into the DOM...
  mounted(el) {
    // Focus the element
    el.focus()
  }
})
1
2
3
4
5
6
7
8
9

If you want to register a directive locally instead, components also accept a directives option:

directives: {
  focus: {
    // directive definition
    mounted(el) {
      el.focus()
    }
  }
}
1
2
3
4
5
6
7
8

Then in a template, you can use the new v-focus attribute on any element, like this:

<input v-focus />
1

# Hook Functions

A directive definition object can provide several hook functions (all optional):

  • beforeMount: called when the directive is first bound to the element and before parent component is mounted. This is where you can do one-time setup work.

  • mounted: called when the bound element's parent component is mounted.

  • beforeUpdate: called before the containing component's VNode is updated

Note

We'll cover VNodes in more detail later, when we discuss render functions.

  • updated: called after the containing component's VNode and the VNodes of its children have updated.

  • beforeUnmount: called before the bound element's parent component is unmounted

  • unmounted: called only once, when the directive is unbound from the element and the parent component is unmounted.

You can check the arguments passed into these hooks (i.e. el, binding, vnode, and prevVnode) in Custom Directive API

# Dynamic Directive Arguments

Directive arguments can be dynamic. For example, in v-mydirective:[argument]="value", the argument can be updated based on data properties in our component instance! This makes our custom directives flexible for use throughout our application.

Let's say you want to make a custom directive that allows you to pin elements to your page using fixed positioning. We could create a custom directive where the value updates the vertical positioning in pixels, like this:

<div id="dynamic-arguments-example" class="demo">
  <p>Scroll down the page</p>
  <p v-pin="200">Stick me 200px from the top of the page</p>
</div>
1
2
3
4
const app = Vue.createApp({})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.value is the value we pass to directive - in this case, it's 200
    el.style.top = binding.value + 'px'
  }
})

app.mount('#dynamic-arguments-example')
1
2
3
4
5
6
7
8
9
10
11

This would pin the element 200px from the top of the page. But what happens if we run into a scenario when we need to pin the element from the left, instead of the top? Here's where a dynamic argument that can be updated per component instance comes in very handy:

<div id="dynamicexample">
  <h3>Scroll down inside this section ↓</h3>
  <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
1
2
3
4
const app = Vue.createApp({
  data() {
    return {
      direction: 'right'
    }
  }
})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.arg is an argument we pass to directive
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})

app.mount('#dynamic-arguments-example')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Result:

See the Pen Custom directives: dynamic arguments by Vue (@Vue) on CodePen.

Our custom directive is now flexible enough to support a few different use cases. To make it even more dynamic, we can also allow to modify a bound value. Let's create an additional property pinPadding and bind it to the <input type="range">




 


<div id="dynamicexample">
  <h2>Scroll down the page</h2>
  <input type="range" min="0" max="500" v-model="pinPadding">
  <p v-pin:[direction]="pinPadding">Stick me {{ pinPadding + 'px' }} from the {{ direction }} of the page</p>
</div>
1
2
3
4
5




 




const app = Vue.createApp({
  data() {
    return {
      direction: 'right',
      pinPadding: 200
    }
  }
})
1
2
3
4
5
6
7
8

Now let's extend our directive logic to recalculate the distance to pin on component update:







 
 
 
 


app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  },
  updated(el, binding) {
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})
1
2
3
4
5
6
7
8
9
10
11

Result:

See the Pen Custom directives: dynamic arguments + dynamic binding by Vue (@Vue) on CodePen.

# Function Shorthand

In previous example, you may want the same behavior on mounted and updated, but don't care about the other hooks. You can do it by passing the callback to directive:

app.directive('pin', (el, binding) => {
  el.style.position = 'fixed'
  const s = binding.arg || 'top'
  el.style[s] = binding.value + 'px'
})
1
2
3
4
5

# Object Literals

If your directive needs multiple values, you can also pass in a JavaScript object literal. Remember, directives can take any valid JavaScript expression.

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
1
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})
1
2
3
4

# Usage on Components

When used on components, custom directive will always apply to component's root node, similarly to non-prop attributes.

<my-component v-demo="test"></my-component>
1
app.component('my-component', {
  template: `
    <div> // v-demo directive will be applied here
      <span>My component content</span>
    </div>
  `
})
1
2
3
4
5
6
7

Unlike attributes, directives can't be passed to a different element with v-bind="$attrs".

With fragments support, components can potentially have more than one root nodes. When applied to a multi-root component, directive will be ignored and the warning will be thrown.

Deployed on Netlify.
Last updated: 10/27/2020, 7:18:08 PM