.agents/skills/vue-best-practices/references/directives.md
Impact: MEDIUM - Directives are for low-level DOM access. Use them sparingly, keep them side-effect safe, and prefer components or composables when you need stateful or reusable UI behavior.
unmounted<script setup> with the v- prefixDirective bindings are not reactive storage. Don’t write to them.
const vFocus = {
mounted(el, binding) {
// binding.value is read-only
el.focus()
}
}
Directives apply to DOM elements. When used on components, they attach to the root element and can break if the root changes.
BAD:
<MyInput v-focus />
GOOD:
<!-- MyInput.vue -->
<script setup>
const vFocus = el => el.focus()
</script>
<template>
<input v-focus>
</template>
unmountedAny timers, listeners, or observers must be removed to avoid leaks.
const vResize = {
mounted(el) {
const observer = new ResizeObserver(() => {})
observer.observe(el)
el._observer = observer
},
unmounted(el) {
el._observer?.disconnect()
}
}
If you only need mounted/updated, use the function form.
const vAutofocus = el => el.focus()
v- Prefix and Script Setup Registration<script setup>
const vFocus = el => el.focus()
</script>
<template>
<input v-focus>
</template>
Use Directive<Element, ValueType> so binding.value is typed, and augment Vue's template types so directives are recognized in SFC templates.
BAD:
// Untyped directive value and no template type augmentation
export const vHighlight = {
mounted(el, binding) {
el.style.backgroundColor = binding.value
}
}
GOOD:
import type { Directive } from 'vue'
type HighlightValue = string
export const vHighlight = {
mounted(el, binding) {
el.style.backgroundColor = binding.value
}
} satisfies Directive<HTMLElement, HighlightValue>
declare module 'vue' {
interface ComponentCustomProperties {
vHighlight: typeof vHighlight
}
}
getSSRPropsDirective hooks such as mounted and updated do not run during SSR. If a directive sets attributes/classes that affect rendered HTML, provide an SSR equivalent via getSSRProps to avoid hydration mismatches.
BAD:
const vTooltip = {
mounted(el, binding) {
el.setAttribute('data-tooltip', binding.value)
el.classList.add('has-tooltip')
}
}
GOOD:
const vTooltip = {
mounted(el, binding) {
el.setAttribute('data-tooltip', binding.value)
el.classList.add('has-tooltip')
},
getSSRProps(binding) {
return {
'data-tooltip': binding.value,
'class': 'has-tooltip'
}
}
}
If a standard attribute or binding works, use it instead of a directive.
Use a directive for DOM-level behavior. Use a component when behavior affects structure, state, or rendering.