Attribute Inheritance
Shelf gets a SearchBox component. On one page the parent writes <SearchBox class="wide" placeholder="Find a book" />. SearchBox never declared class or placeholder as props, yet something happens to them. Knowing where they go saves an afternoon of "why is my input not wide?"
The rule
Undeclared attributes fall through to the single root element.
Anything a parent passes that the child did not claim with defineProps or defineEmits is a fallthrough attribute. That includes class, style, id, placeholder, and v-on listeners such as @click. If the component renders one root element, Vue adds them to it automatically.
<!-- SearchBox.vue, first version -->
<template>
<label class="search">
Search
<input type="search" />
</label>
</template>With <SearchBox class="wide" placeholder="Find a book" />, the root label becomes class="search wide" placeholder="Find a book". The class merges nicely with the existing one. The placeholder, sadly, sits on the label, where it does nothing.
To choose the destination yourself, switch off automatic inheritance and bind $attrs where you want it:
<!-- SearchBox.vue, fixed -->
<script setup>
defineOptions({ inheritAttrs: false })
</script>
<template>
<label class="search">
Search
<input type="search" v-bind="$attrs" />
</label>
</template>Now placeholder, class and any listener land on the input. In script code the same object is available through useAttrs() from vue.
Several root elements: if a template has more than one top-level element, Vue cannot guess which should receive the attributes. It adds them nowhere and warns, unless you bind $attrs explicitly on one of them.
The shortcut
For every attribute the parent passes, run a three-step check: Declared? Single root? Opted out?
Declared?
Is
placeholdera prop or emit of SearchBox? No. So it is a fallthrough attribute.
This is also why declaring emits matters. Listen with @remove on a component that did not declare remove, and the listener falls through to the root element as a native DOM listener instead.
Drills
Answer fast — trust the rule.
A single-root component gets id="main-search" from its parent and has no id prop. Where does it go?
The root already has class="search" and the parent passes class="wide". What is the result?
How do you stop attributes from landing on the root so you can place them yourself?
A component has two root elements and does not bind $attrs. What happens to fallthrough attributes?
Remember
- Attributes and listeners the child does not declare fall through to its single root element.
- Shortcut: declared? single root? opted out?
- Trap: an attribute meant for an inner input landing on a wrapper; fix with
inheritAttrs: falseplusv-bind="$attrs".
