Files
agent-skills/skills/vue-best-practices/reference/directive-avoid-on-components.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

3.6 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Avoid Using Custom Directives on Components HIGH Custom directives on multi-root components are silently ignored, causing unexpected behavior gotcha
vue3
directives
components
multi-root
best-practices

Avoid Using Custom Directives on Components

Impact: HIGH - Using custom directives on components is not recommended and can lead to unexpected behavior. When applied to a multi-root component, the directive will be ignored and a warning will be thrown. Unlike attributes, directives cannot be passed to a different element with v-bind="$attrs".

Custom directives are designed for direct DOM manipulation on native HTML elements, not Vue components.

Task Checklist

  • Only apply custom directives to native HTML elements, not components
  • If a component needs directive-like behavior, consider making it part of the component's API
  • For components, use props and events instead of directives
  • If you must use a directive on a component, ensure it has a single root element

Incorrect:

<template>
  <!-- WRONG: Directive on a component - may be ignored -->
  <MyComponent v-focus />

  <!-- WRONG: Multi-root component - directive is ignored with warning -->
  <MultiRootComponent v-highlight />
</template>

<script setup>
import MyComponent from './MyComponent.vue'
import MultiRootComponent from './MultiRootComponent.vue'

// MultiRootComponent.vue has:
// <template>
//   <div>First root</div>
//   <div>Second root</div>
// </template>
</script>

Correct:

<template>
  <!-- CORRECT: Directive on native HTML element -->
  <input v-focus />

  <!-- CORRECT: Use props/events for component behavior -->
  <MyComponent :should-focus="true" />

  <!-- CORRECT: Wrap component in element if directive is needed -->
  <div v-highlight>
    <MyComponent />
  </div>
</template>

<script setup>
import MyComponent from './MyComponent.vue'
</script>

When a Directive on Component Works

Directives only work reliably on components with a single root element. The directive applies to the root node, similar to fallthrough attributes:

<!-- SingleRootComponent.vue -->
<template>
  <div>I am the only root</div>
</template>

<!-- Parent.vue -->
<template>
  <!-- This works because SingleRootComponent has one root -->
  <SingleRootComponent v-my-directive />
</template>

However, this is still not recommended because:

  1. It's fragile - refactoring to multi-root breaks the directive silently
  2. It's unclear which element receives the directive
  3. The component author may not expect external DOM manipulation

Better Patterns

Option 1: Component Prop

<!-- FocusableInput.vue -->
<template>
  <input ref="inputRef" v-bind="$attrs" />
</template>

<script setup>
import { ref, onMounted, watch } from 'vue'

const props = defineProps({
  autofocus: Boolean
})

const inputRef = ref(null)

onMounted(() => {
  if (props.autofocus) {
    inputRef.value?.focus()
  }
})
</script>

<!-- Usage -->
<FocusableInput autofocus />

Option 2: Exposed Method

<!-- FocusableInput.vue -->
<template>
  <input ref="inputRef" />
</template>

<script setup>
import { ref } from 'vue'

const inputRef = ref(null)

const focus = () => inputRef.value?.focus()

defineExpose({ focus })
</script>

<!-- Parent.vue -->
<template>
  <FocusableInput ref="myInput" />
</template>

<script setup>
import { ref, onMounted } from 'vue'

const myInput = ref(null)

onMounted(() => {
  myInput.value?.focus()
})
</script>

Reference