Vue developers often use directives to quickly manipulate DOM elements in their applications. Directives make code cleaner and easier to read and encourage code reuse. The ability to create custom directives gives Vue a bit of an edge over React, too.
This tutorial will walk you through Vue’s custom directives and illustrate how to implement them in your project. You’ll also learn how they simplify your development process and help build some handy features.
The only prerequisite for this guide is working knowledge of JavaScript, CSS, and Vue.
Jump ahead:
To learn custom directives, it’s best to get familiar with the basic concepts of Vue directives first.
Vue directives are special attributes that are used to interact with the DOM. In other words, when used, a directive applies side effects or modifications to the DOM. Optional properties of a directive enable you to define possible side effects.
Directives in Vue always have a v-
prefix. Some examples of default directives are v-if
, v-else
, v-show
, and v-model
. You can see the full list here.
The following image illustrates the structure of a Vue directive:
The expression, argument, and modifiers are optional; their implementation may vary based on the task at hand. A directive can only accept one expression at a time.
Let’s look at a basic example to understand what that means. I assume you know how to create projects with the Vue CLI. If you are not using the Vue CLI right now, you can also use CodePen like I have to demonstrate in this tutorial.
Take a look at the v-if
and v-else
directives as shown in the example below. These are for conditionally displaying messages and actions for fake logged-in
and logged-out
states, respectively.
See the Pen
Vue Directives Examples: v-if, v-else by Rahul (@_rahul)
on CodePen.
The value of the v-if
directive inside double quotes is our expression here. The value of isUserLoggedIn
is true
, therefore the app will show a welcome message. Similarly, setting it to false
will pop up a login button in the browser.
As you can see, the v-else
directive doesn’t have an expression value, argument, or modifier attached. This shows that all of the properties of a directive are completely optional.
Now, if you observe the DOM closely, you’ll see that only one element renders based on the isUserLoggedIn
variable’s value.
A directive can have one argument at a time and always begins with a colon after the directive name. Let’s keep going with our login/logout example and learn about the directive arguments using the v-on
directive.
Previously, we manually changed the value of isUserLoggedIn
in the code to showcase conditional directives. This example shows how we can control it with a click of a button.
See the Pen
Vue Directives Examples: Arguments with v-on by Rahul (@_rahul)
on CodePen.
Here, click
is an argument to the v-on
directive. Now, if we click the login button, we receive a login success message.
It’s ideal to use login input elements in an HTML form element, then wrap our message and button in an HTML form. Also, rather than adding an event to the button, let’s hoist v-on
one level up to the form and add a submit
argument instead.
Adding the submit
argument to v-on
will provide the onSubmit
functionality to our form. We don’t need a v-on:click
event for our button anymore as it will now act as the submit button by itself.
But forms that have an onSubmit
attribute reload the whole page on their submission. We don’t need that here for now. To stop the form from reloading, we can use a prevent
modifier.
See the Pen
Vue Directives Examples: Argument Modifiers with v-on by Rahul (@_rahul)
on CodePen.
In the example above, the prevent
modifier works just like the e.preventDefault()
JavaScript method. It simply stops the form from reloading the page.
A modifier always starts with a dot and can be chained together with other modifiers. We will get to see an example of modifier chaining later on in the tutorial.
The directives you add using the Directive API are custom directives. You as a developer have total control over choosing a name, arguments, modifiers, and functionality for a custom directive.
There are two ways to define a custom directive in Vue. The first is the standard, which makes the directive available globally in the app:
const app = Vue.createApp({}) app.directive("test", { // TODO })
You can use this v-test
directive anywhere in the app, but it doesn’t do anything right now, so we will add some functionality to it. Alternatively, you can use the directives
option for components, which makes the directive available in the local context of that component.
Since Vue handles directive prefixing by itself, you should avoid adding v-
when defining a directive.
Let’s define a directive that changes the text color of any given element to blue.
See the Pen
Custom Directives Example: v-blue by Rahul (@_rahul)
on CodePen.
However, this is not as efficient, as it can only make the text blue. Instead of hard coding a color value in the directive, we can improve it by taking in a custom color.
See the Pen
Custom Directives Example: v-color by Rahul (@_rahul)
on CodePen.
It looks much better now. We used the mounted
lifecycle hook for our directive, which is apt for adding regular DOM operations on our element. We will discuss more on lifecycle hooks in an upcoming section.
It’s important to notice that we have enclosed our color values inside single quotes. This is because Vue directives take an expression for the value, not just a plain value (a string, in this case).
We also made our color look more like an expression by placing single quotes around its value. Values for directives can be anything that is accepted as a valid JavaScript expression.
It is possible to pass multiple color values to the directive at the same time and then apply them to the bound element later on. For example, we can pass different values for text color and background color at the same time.
Let’s pass an object literal with color values in the directive and then set things up in the mounted hook accordingly.
See the Pen
Custom Directives Example: Advanced v-color by Rahul (@_rahul)
on CodePen.
In the above example, we used the destructuring assignment in JavaScript to simplify the value assignment from the object literal.
Getting back to hooks, what is the purpose of a mounted hook and others of its kind? Let us learn more about these directive hooks next.
The Directive API provides a set of lifecycle hooks: created
, mounted
, beforeUpdate
, updated
, beforeUnmount
, and unmounted
.
As their names suggest, each of these hooks has a different function. Let’s examine them one by one:
created
hook is called just before the attributes or event listeners of the bound element are applied. If you have event listeners that you must call before normal v-on
event listeners are attached, this is quite usefulmounted
hook is called before the bound element’s parent component is mounted. It is where we perform our DOM operations in general, which defines the functioning of our directivebeforeUpdate
hook is called just before the vNode
of the parent element is updated. Say I have a click listener attached to an element that changes its current value. Now, before that value updates, beforeUpdate
is firedupdated
hook is called just after the vNodes of the containing element and its children are updated. From the last example, the updated hook will execute just after beforeUpdate
beforeUnmount
is called before the parent element of the bound element is unmountedunmounted
hook is called only once, when the parent element is unmounted. The directive gets unbound from the element in this caseEach of these hooks accept three arguments: naming, binding, and vNode. Before we go over the hook arguments, I want to show you how hooks actually function in Vue. A full view is recommended to see the updates in the hook log.
See the Pen
Custom Vue Directives Example: Lifecycle Hooks by Rahul (@_rahul)
on CodePen.
Just type in something in the text box to see them in action.
As discussed in the last segment, directive hooks have certain arguments as well.
el
specifies the element our directive is bound to. We use this to target our element and to apply modifications to it.
The binding argument contains directive properties such as current value
, oldValue
, arg
, and modifiers
. We’ll see the use of arg
and modifiers
as we go further in the tutorial. Without these two, it is not possible to create arguments and modifiers for a directive in Vue.
The vNode
argument is basically a copy of the real DOM object for el
. The prevNode
argument is the previous state of el
, and only available in the beforeUpdate
and updated
hooks.
I would encourage you to see directive hooks and their arguments in action in this CodePen. Observe the nature of these arguments in the console; this will help you do advanced things with Vue directives in the future.
Now that we are familiar with custom directives, it’s time to create something a bit more advanced, like a pull quote.
The concept of pull quotes is inspired by magazine and newspaper design, and consists of small quotations pulled from the existing text of an article. We can therefore select a text fragment with a tag (e.g., span
), clone it with JavaScript, and then style the clone to create a pull quote with CSS.
Before we move to the JavaScript part, let’s discuss a bit about the CSS we need to scaffold and style our pull quote.
The standard is to use CSS properties. We can either inject CSS properties directly into the respective element, like we did in some of the examples above, or declare CSS classes and use them with the classList
Web API.
The latter approach is better, because it allows us to keep things well organized.
In order to keep things simple, I have chosen to use simple CSS styles for our pull quote example. CSS floats and text alignments will help align our pull quotes left and right. Some basic margin and padding properties will be used to create space and size.
In order to keep our example mobile-first, we will also add a short and simple media query.
Starting from scratch, place some HTML dummy data in our fresh project that will serve as our article. We will use fragments of this data to make the pull quotes:
<div id="app"> <article class="content"> <p>...</p> <p>...</p> <p>...</p> … </article> </div>
We’ll now add Normalize.CSS to our project. If you are on CodePen, simply click the gear icon in the CSS panel, and select the Normalize option.
Next, defining a content width will be a good idea, as it will provide us with space to place our quotes in either the left or right directions comfortably:
.content { margin-inline: auto; max-width: 640px; }
The above CSS gives the content a width of 640px and aligns it to the center of the browser. I’ve also added some padding to the app to keep it away from the edges.
Now let’s write some CSS for our pull quote’s default state:
.pulled-quote { font-size: 1.1em; font-weight: bold; display: block; text-align: center; color: #999; padding-block: 1em; margin-block: 1.5em; }
This is how our pull quote will look on smaller devices. As you can see, it’s nothing more than some alignment and font properties to decorate. Let’s also write the left and right variants of the same, which will only work when enough screen real estate is available:
@media only screen and (min-width: 1024px) { .pulled-quote:not(.pulled-quote--center) { width: 300px; } .pulled-quote--right { float: right; margin: 0 -200px 0 0; padding: 0 0 1.5em 1.5em; text-align: left; } .pulled-quote--left { float: left; margin: 0 0 0 -200px; padding: 0 1.5em 1.5em 0; text-align: right; } } We are using float properties to move the element from the regular flow to the direction specified. Text alignments and padding are applied to support floating. We are also using a negative margin to break the element out of the container.
Now that we have enough CSS to support our pull quote, let’s write a directive and see how a copy of the bounded element is created to construct it:
const app = Vue.createApp({}); app.directive("pq", { mounted(el, binding) { var elParent = el.parentNode var elClone = el.cloneNode(true) elParent.prepend(elClone) elClone.classList.add("pulled-quote") } }
Using the cloneNode
and prepend
JavaScript functions of the Web API, it is simple to copy a node and add it to an element’s parent. The clasList.add
function can then add the .pull-quote
CSS class.
With no directive arguments provided, or by default, our pull quote should align to the left. Keeping that in mind, let’s add some directive arguments to it:
const app = Vue.createApp({}); app.directive("pq", { mounted(el, binding) { var elParent = el.parentNode var elClone = el.cloneNode(true) elParent.prepend(elClone) elClone.classList.add("pulled-quote") switch(binding.arg) { case 'right': elClone.classList.add("pulled-quote--right") break case 'center': elClone.classList.add("pulled-quote--center") break default: elClone.classList.add("pulled-quote--left") break } } }
Because a directive only accepts one argument at a time, we used the switch block for each of our cases. You can see that the default case adds the "pulled-quote--left"
CSS to the bound element.
We’ll now apply the directive to some of our dummy data and see how it works.
See the Pen
Custom Directives Example: Pull Quotes by Rahul (@_rahul)
on CodePen.
As you can see, our pull quotes are functional now. Similarly, we can also add some modifiers to the directive to add little details like italics, highlight, and quoted text:
const app = Vue.createApp({}); app.directive("pq", { mounted(el, binding) { var elParent = el.parentNode var elClone = el.cloneNode(true) elParent.prepend(elClone) elClone.classList.add("pulled-quote") switch(binding.arg) { … } if(binding.modifiers.italic) { elClone.style.fontStyle = "italic" } if(binding.modifiers.quoted) { elClone.classList.add("pulled-quote--quoted") } if(binding.modifiers.highlighted) { elClone.classList.add("pulled-quote--highlighted") } } }
In addition, I would like to incorporate the functionality of abbreviating longer text fragments into pull quotes. Now, if you pass a string as an expression, it can function as an abbreviation.
Here is the working example of all that we did above. I added some fonts and cosmetics to make it look neater and more appealing.
See the Pen
Custom Directives Example: Pull Quotes by Rahul (@_rahul)
on CodePen.
With the v-pq
directive, we can now convert any text block into a pull quote with different alignment and styling options. I would also like to see your additions to this!
In this tutorial, we learned what directives in Vue are, why they are useful, and how to use them. We then moved on to see examples of custom directives, as well as lifecycle hooks and hook arguments from the Vue Directive API.
With all that knowledge, we finally created a directive that adds a pull quote behavior to any text fragment in our Vue application.
I hope you learned something new about custom Vue directives through this tutorial — I appreciate you reading all the way to the end. Feel free to ask me any questions or make suggestions in the comments section below.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn to build scalable micro-frontend applications using React, discussing their advantages over monolithic frontend applications.
Build a fully functional, real-time chat application using Laravel Reverb’s backend and Vue’s reactive frontend.
console.time is not a function
errorExplore the two variants of the `console.time is not a function` error, their possible causes, and how to debug.
jQuery 4 proves that jQuery’s time is over for web developers. Here are some ways to avoid jQuery and decrease your web bundle size.