Custom Directives and Plugins
|
This section documents the current Vue 3.x release line as published at
the official Vue.js documentation, which is the reference these pages are written and
verified against. No specific patch version is pinned. The Composition API with This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since Vue and its ecosystem iterate quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Custom directives and plugins are Vue’s two extension points for logic that is not a component. A directive
packages low-level DOM access you attach to an element with a v- attribute; a plugin is an object that
installs app-wide features — components, directives, global properties, injected values — through a single
app.use() call.
Custom directives
A directive is an object of lifecycle hooks that each receive the raw element. Inside <script setup>, any
camelCase binding named vSomething is usable as v-something in that component’s template:
<script setup>
// registers v-focus for this component only
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
Use a directive when you genuinely need direct DOM manipulation that a binding or component cannot express — managing focus, integrating a non-Vue widget, custom scroll behaviour. See Custom Directives.
The hook set
Every hook is optional and receives (el, binding, vnode, prevVnode):
| Hook | Called |
|---|---|
|
before the element’s attributes and event listeners are applied |
|
before the element is inserted into the DOM |
|
after the element and its parent component are mounted |
|
before the containing component re-renders |
|
after the component and its children have updated |
|
before the element is unmounted |
|
after the element has been removed |
The list and each hook’s timing are in Directive Hooks.
Function shorthand
When the behaviour is the same on mounted and updated, pass a function instead of an object — it is
registered as both hooks:
app.directive('color', (el, binding) => {
el.style.color = binding.value // runs on mounted AND updated
})
The binding object
The second argument carries the parsed template usage. For v-pin:right.far="offset":
| Property | Value |
|---|---|
|
the current value of the expression — here whatever |
|
the previous value; available in |
|
|
|
an object of flags — here |
|
the component instance that is using the directive |
app.directive('pin', (el, binding) => {
el.style.position = 'fixed'
const side = binding.arg || 'top'
const offset = binding.modifiers.far ? '64px' : '16px'
el.style[side] = offset
})
// <span v-pin:right.far="true">pinned</span>
Local vs. global registration
Global registration — app.directive('focus', \{ … }) in main.js — makes the directive available
everywhere but invisible at the point of use. Prefer local registration: the vFocus object in
<script setup>, or the directives option in an Options-API component:
// Options API -- local registration (contrast form)
export default {
directives: {
focus: { mounted: (el) => el.focus() }
}
}
Directives on components
Discouraged. On a component with a single root node the directive applies to that root, like a fallthrough attribute; on a component with multiple root nodes it is ignored and Vue emits a warning. Prefer a prop or a wrapper element. See Usage on Components.
Plugins
A plugin is any object exposing an install(app, options) method (or a plain function used as that method).
app.use(plugin, options) invokes it once and ignores repeat calls for the same plugin. Inside install
you have the full application API — register components and directives, add global properties, or provide
injectable values:
// plugins/i18n.js
export default {
install(app, options) {
const dict = options.messages ?? {}
const translate = (key) => dict[key] ?? key
// 1. a global property -- usable as $t in every template
app.config.globalProperties.$t = translate
// 2. an injectable value -- for inject('i18n') in <script setup>
app.provide('i18n', { t: translate })
// 3. ship a component and a directive with the plugin
app.component('I18nText', {
props: ['k'],
setup: (props) => () => translate(props.k)
})
app.directive('t', (el, binding) => {
el.textContent = translate(binding.value)
})
}
}
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './plugins/i18n.js'
createApp(App)
.use(i18n, { messages: { hello: 'Hola', bye: 'Adios' } })
.mount('#app')
Consume the provided value in a component:
<script setup>
import { inject } from 'vue'
const i18n = inject('i18n')
</script>
<template>
<h1>{{ i18n.t('hello') }}</h1>
<p v-t="'bye'"></p>
</template>
The install contract and app.use are documented in
Plugins; providing values app-wide is
app.provide; and to give $t a type on
ComponentCustomProperties, see
Augmenting Global Properties.
A toast plugin
A second, smaller example — a plugin that mounts its own host element and exposes an imperative push:
// plugins/toast.js
import { createApp, reactive } from 'vue'
import ToastHost from './ToastHost.vue'
export default {
install(app) {
const state = reactive({ items: [] })
const push = (text) => state.items.push({ id: Date.now(), text })
const mountPoint = document.createElement('div')
document.body.appendChild(mountPoint)
createApp(ToastHost, { state }).mount(mountPoint)
app.provide('toast', { push })
app.config.globalProperties.$toast = push
}
}
Every mainstream Vue library follows this shape — app.use(pinia), app.use(router),
app.use(i18n). See Routing for the router’s own app.use(router) call.
Mixins: legacy only
Before the Composition API, a mixin shared option fragments between components:
// legacy pattern -- avoid in new code
export const paginationMixin = {
data: () => ({ page: 1 }),
methods: { next() { this.page++ } }
}
export default {
mixins: [paginationMixin]
}
Merged options hide where page and next came from, two mixins can silently collide on a name, and a
mixin takes no arguments. The Vue documentation now points to
composables for stateful reuse, custom directives for DOM logic, and plugins
for app-level installation — see
Composables vs. Mixins.
See also
-
Composables — the preferred mechanism for sharing stateful logic.
-
Routing — Vue Router installed as a plugin with
app.use. -
JavaScript Development — the module and closure semantics
installand directive hooks rely on.