skip to content

In Vue 3, what happens when you apply a custom directive to a component instead of a plain element, and why is it discouraged?

level: middleimportance: nice to knowfreq 25%

answer

  1. lands on the root node
  2. like fallthrough attributes
  3. multi-root: warning, ignored
  4. cannot be forwarded via $attrs
  5. el depends on child markup

basics

~20 s

The directive is applied to the component's root element, much like fallthrough attributes. With several root nodes it is ignored and a development warning appears, and unlike attributes it cannot be redirected with v-bind="$attrs", so the docs advise against it.

solid answer

~40 s

When you write `<UserAvatar v-tooltip="name" />`, Vue passes the directive down to the component's **root node**, the same way fallthrough attributes land there, and `el` in the hooks is that root element. If the component renders multiple root nodes, or a root that is not an element, development builds warn that the runtime directive was used on a component with a non-element root node and it will not function as intended. Unlike attributes, directives cannot be moved to a different inner element with `v-bind="$attrs"`. That makes the directive's behaviour depend on the child's private markup: a refactor that wraps the root in a fragment silently breaks it. The Vue docs mark directives on components as not recommended; wrap a plain element around the component or let the component offer the behaviour itself.

code

vue · 12 lines
vue
<script setup>
import TextField from './TextField.vue'
import { vTooltip } from './directives/tooltip'
</script>

<template>
  <!-- risky: binds to TextField's root, whatever that is -->
  <TextField v-tooltip="'Your legal name'" />

  <!-- explicit: the wrapper element is the target -->
  <span v-tooltip="'Your legal name'"><TextField /></span>
</template>

go deeper

for a junior

Know that a directive on a component lands on its root element.

for a middle

Explain the multi-root warning and why $attrs cannot redirect a directive.

for a senior

Refactor call sites so directives target explicit elements or props owned by the component.

for a principal

Set a library policy on whether components promise a single element root for directive users.

## What Vue does Custom directives are designed for **plain elements**. When one is placed on a component tag, Vue does not give the component a way to receive it. Instead, while rendering the child, it attaches the directive to the child's **root vnode**, much as it does with fallthrough attributes: ```vue-html <!-- parent --> <UserAvatar v-tooltip="user.name" /> <!-- UserAvatar's template --> <div class="avatar"><img :src="src" /></div> ``` Here the tooltip binds to the `<div class="avatar">`. The hooks' `el` is that div, and `binding.instance` is the **parent** component, whose template wrote the directive. ## When the root is not a single element Components can render more than one root node, or a root that is itself a fragment. In that case development builds log a warning that a runtime directive was used on a component with a non-element root node and will not function as intended. The docs summarise the result: on a multi-root component the directive is ignored and a warning is thrown. | Child's root | Directive result | |---|---| | One element | applied to that element | | Several root nodes | not applied as intended; dev warning | | Root is another component with one element root | passed down to that element | ## Why you cannot forward it For attributes, a multi-root or wrapper component can choose where they go with `inheritAttrs: false` plus `v-bind="$attrs"`. **Directives are not attributes**: they are never collected into `$attrs`, so the child cannot redirect them to an inner `<input>` or button. The child has no say over where the directive lands. ## Why the docs discourage it The Vue guide labels custom directives on components as **not recommended**, because unexpected behaviour may occur with multiple root nodes. In practice the problems are: - **Hidden coupling.** The directive depends on the child's current root markup, which is an internal detail. - **Fragile refactors.** Adding a sibling root element, such as a visually hidden label, silently disables the directive in production, where the warning is stripped. - **Wrong target.** For a text-field component, `v-focus` on the component lands on the wrapper `<div>`, not the inner `<input>`. ## Better options 1. **Wrap a plain element:** `<span v-tooltip="name"><UserAvatar /></span>` makes the target explicit. 2. **Let the component own the behaviour:** accept a `tooltip` prop, or expose a `focus()` method, and apply the directive inside its own template on the correct element. 3. **Document single-root components** if a library intentionally supports directives on them, and treat adding a root node as a breaking change. ## Interview summary Say where it lands (the root element), what happens with multiple roots (warning, not applied), why `$attrs` cannot help, and what to do instead. ## A hidden cost: forced child re-renders There is also a performance effect that rarely gets mentioned. When Vue decides whether a child component needs to re-render after its parent re-renders, it normally compares props and slots and skips the child if nothing changed. A runtime directive on the component's vnode **bypasses that check**: the runtime forces the child to update so that the directive's `beforeUpdate` and `updated` hooks can run against the root element. For a cheap child this is noise, but a directive on a heavy component, such as a data table, makes it re-render on every parent update even when none of its inputs changed. ## A refactor that breaks it Imagine a `<TextField>` whose template is a single `<div>` wrapping a label and an input. A team member adds a sibling `<p class="hint">` next to the wrapper, making the component multi-root. Every `v-tooltip` written on `<TextField>` elsewhere stops working. In development the console shows the directive warning; in production nothing is reported. Nothing in `<TextField>`'s own code or tests mentions tooltips, which is exactly the hidden coupling the docs warn about.

  • In Vue 3, can a multi-root component use `v-bind="$attrs"` to route a parent's custom directive to its inner input?
    No. `$attrs` holds fallthrough attributes and listeners only; directives are never part of it. The directive is attached to the root vnode, and with multiple roots it does not work as intended. The component should accept a prop or apply the directive internally on the right element.
  • In Vue 3, what is `binding.instance` when a directive is written on a component tag in a parent's template?
    The parent's public instance, because the parent's template contains the directive. The child component whose root element receives the directive is not the instance; the directive only gets that root as `el`.

saying these in an interview costs you the question

  • Expects the directive to bind to the component's inner input
  • Thinks v-bind="$attrs" forwards directives
  • Believes Vue applies the directive to every root node
  • Assumes the multi-root warning also appears in production
  • Thinks binding.instance is the child component
  • Assumes a directive on a component never affects its re-rendering