In Vue 3, $attrs aggregates all attributes passed from a parent to a child component that are not explicitly declared via props. It replaces the separate $listeners found in Vue 2. Understanding its behavior across different component layouts and API styles is essential for flexible attribute forwarding.
Attribute Forwarding in Templates
Single Root Element
When a component has exactly one root node, any undeclared incoming attribute are automatically bound to that root element.
<!-- ParentComponent.vue -->
<template>
<SingleRoot msg="hello" extra="value" style="color: blue;" />
</template>
<script setup>
import SingleRoot from './SingleRoot.vue';
</script>
<!-- SingleRoot.vue -->
<template>
<div>{{ msg }}</div>
</template>
<script setup>
defineProps({ msg: String });
</script>
Here, extra and inline style are applied directly to the root <div>, turning the text blue. To access a specific undeclared attribute inside the template:
<template>
<div>{{ $attrs.extra }}</div>
</template>
Multiple Root Elements
With more than one root node, automatic binding does not occur. Attributes must be manually assigned using v-bind.
<!-- MultiRoot.vue -->
<template>
<section>{{ msg }}</section>
<section v-bind="$attrs">
{{ msg }}
</section>
</template>
<script setup>
defineProps({ msg: String });
</script>
The second <section> receives all undeclared attributes. For selective binding:
<section :style="$attrs.style">
{{ msg }}
</section>
This approach ensures precise control over which attributes apply to which elements.
Accessing Attributes in JavaScript
Options API
Attributes are available on the instance as this.$attrs.
<script>
export default {
props: { msg: String },
mounted() {
console.log(this.$attrs);
}
};
</script>
Composition API (Pre-3.2 Syntax)
Use the setup(props, ctx) signature; attrs resides in ctx.attrs.
<script>
export default {
props: { msg: String },
setup(props, ctx) {
console.log(ctx.attrs);
}
};
</script>
Composition API (3.2+ Syntax with <script setup>)
Import useAttrs to retrieve the collection of undeclared attributes.
<script setup>
import { useAttrs } from 'vue';
const definedProps = defineProps({ msg: String });
const attrCollection = useAttrs();
console.log(definedProps, attrCollection);
</script>
attrCollection behaves like a reactive object; individual entries are accessed via attrCollection.name, attrCollection.style, etc. This method aligns with the modern SFC compilation model and avoids boilerplate setup functions.
By leveraging $attrs appropriately—whether auto-bound to a sole root element, manually distributed among multiple roots, or accessed in script logic—you can build components that forward attributes transparently and maintain clean separation between declared props and passthrough data.