Files
stack/packages/mosaic/framework/skills/vue-best-practices/reference/directive-naming-v-prefix.md
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

4.3 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Use v-prefix Naming Convention for Local Directives LOW Proper naming enables automatic directive recognition in script setup best-practice
vue3
directives
naming
script-setup
conventions

Use v-prefix Naming Convention for Local Directives

Impact: LOW - In <script setup>, any camelCase variable starting with the v prefix can automatically be used as a custom directive. This convention enables seamless local directive registration without explicit configuration.

Following this naming pattern ensures Vue correctly recognizes and registers your local directives.

Task Checklist

  • Name local directive variables with v prefix in camelCase (e.g., vFocus, vHighlight)
  • Use the directive in templates without the v prefix lowercase (e.g., v-focus, v-highlight)
  • For multi-word directives, use camelCase in script and kebab-case in template

Incorrect:

<script setup>
// WRONG: No v prefix - won't be recognized as directive
const focus = {
  mounted: (el) => el.focus()
}

// WRONG: Wrong casing
const VFocus = {
  mounted: (el) => el.focus()
}

// WRONG: Kebab-case in script
const 'v-focus' = {  // Syntax error
  mounted: (el) => el.focus()
}
</script>

<template>
  <!-- These won't work -->
  <input focus />
  <input v-Focus />
</template>

Correct:

<script setup>
// CORRECT: v prefix with camelCase
const vFocus = {
  mounted: (el) => el.focus(),
};

const vHighlight = {
  mounted: (el) => {
    el.classList.add('is-highlight');
  },
};

// CORRECT: Multi-word directive
const vClickOutside = {
  mounted(el, binding) {
    el._handler = (e) => {
      if (!el.contains(e.target)) binding.value(e);
    };
    document.addEventListener('click', el._handler);
  },
  unmounted(el) {
    document.removeEventListener('click', el._handler);
  },
};

// CORRECT: Function shorthand with v prefix
const vColor = (el, binding) => {
  el.style.color = binding.value;
};
</script>

<template>
  <!-- Use kebab-case in template -->
  <input v-focus />
  <p v-highlight>Highlighted text</p>
  <div v-click-outside="closeMenu">Dropdown</div>
  <span v-color="'red'">Colored text</span>
</template>

Template Casing Rules

In templates, directives should use kebab-case:

<script setup>
const vMyLongDirectiveName = (el) => {
  /* ... */
};
const vAutoFocusInput = (el) => el.focus();
const vLazyLoadImage = {
  /* ... */
};
</script>

<template>
  <!-- camelCase in script -> kebab-case in template -->
  <div v-my-long-directive-name></div>
  <input v-auto-focus-input />
  <img v-lazy-load-image />
</template>

Options API Registration

Without <script setup>, directives need explicit registration:

// Local registration with Options API
export default {
  directives: {
    // Key is directive name (without v- prefix)
    focus: {
      mounted: (el) => el.focus(),
    },
    highlight: {
      mounted: (el) => el.classList.add('is-highlight'),
    },
    // Multi-word uses camelCase key
    clickOutside: {
      mounted(el, binding) {
        /* ... */
      },
      unmounted(el) {
        /* ... */
      },
    },
  },
};

Global Registration

For global directives, register on the app instance:

// main.js
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

// Global directive - name without v- prefix
app.directive('focus', {
  mounted: (el) => el.focus(),
});

// Multi-word directive
app.directive('click-outside', {
  mounted(el, binding) {
    /* ... */
  },
  unmounted(el) {
    /* ... */
  },
});

// Function shorthand
app.directive('color', (el, binding) => {
  el.style.color = binding.value;
});

app.mount('#app');

Importing Directives

When importing directives, rename to add v prefix:

// directives/focus.js
export const focus = {
  mounted: (el) => el.focus()
}

// In component
<script setup>
import { focus as vFocus } from '@/directives/focus'
// Now usable as v-focus in template
</script>

// Or export with v prefix already
// directives/focus.js
export const vFocus = {
  mounted: (el) => el.focus()
}

// In component
<script setup>
import { vFocus } from '@/directives/focus'
// Directly usable as v-focus
</script>

Reference