Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/inheritattrs-false-for-wrapper-components.md
1# Use inheritAttrs: false for Wrapper Components23## Rule45When building wrapper components where attributes should be applied to an inner element instead of the root element, always set `inheritAttrs: false` and explicitly bind `$attrs` to the target element.67## Why This Matters89- By default, Vue applies all non-prop attributes to the root element10- Wrapper components often have a non-semantic root (div wrapper, label wrapper)11- Attributes like `id`, `aria-*`, `data-*`, and event listeners should target the functional element12- Without `inheritAttrs: false`, accessibility and functionality can break1314## Bad Code1516```vue17<!-- BaseInput.vue - WRONG: attrs go to wrapper div, not input -->18<template>19<div class="input-wrapper">20<label>{{ label }}</label>21<input type="text" />22</div>23</template>2425<script setup>26defineProps(['label'])27</script>2829<!-- Parent usage -->30<BaseInput31id="email"32placeholder="Enter email"33aria-describedby="email-help"34@focus="handleFocus"35/>3637<!--38RESULT: All attrs go to the wrapper div!39<div class="input-wrapper" id="email" placeholder="Enter email" ...>40<label>...</label>41<input type="text" /> <!-- No id, placeholder, or aria! -->42</div>43-->44```4546## Good Code4748```vue49<!-- BaseInput.vue - CORRECT: attrs bound to input element -->50<script setup>51defineProps(['label'])5253defineOptions({54inheritAttrs: false55})56</script>5758<template>59<div class="input-wrapper">60<label>{{ label }}</label>61<input type="text" v-bind="$attrs" />62</div>63</template>6465<!-- Parent usage -->66<BaseInput67id="email"68placeholder="Enter email"69aria-describedby="email-help"70@focus="handleFocus"71/>7273<!--74RESULT: Attrs correctly applied to input75<div class="input-wrapper">76<label>...</label>77<input type="text" id="email" placeholder="Enter email"78aria-describedby="email-help" />79</div>80-->81```8283## Setting inheritAttrs in Different Syntaxes8485### Script Setup (Vue 3.3+)8687```vue88<script setup>89defineOptions({90inheritAttrs: false91})92</script>93```9495### Script Setup (Before Vue 3.3)9697```vue98<script>99export default {100inheritAttrs: false101}102</script>103104<script setup>105// Your setup code here106</script>107```108109### Options API110111```vue112<script>113export default {114inheritAttrs: false,115// other options...116}117</script>118```119120## Common Wrapper Component Patterns121122### Form Input Wrapper123124```vue125<script setup>126import { useAttrs, computed } from 'vue'127128defineProps({129label: String,130error: String131})132133defineOptions({134inheritAttrs: false135})136137const attrs = useAttrs()138139// Separate class/style for wrapper vs input140const inputAttrs = computed(() => {141const { class: _, style: __, ...rest } = attrs142return rest143})144</script>145146<template>147<div class="form-field" :class="{ 'has-error': error }">148<label v-if="label">{{ label }}</label>149<input v-bind="inputAttrs" />150<span v-if="error" class="error">{{ error }}</span>151</div>152</template>153```154155### Button with Icon Wrapper156157```vue158<script setup>159defineProps({160icon: String,161iconPosition: {162type: String,163default: 'left'164}165})166167defineOptions({168inheritAttrs: false169})170</script>171172<template>173<button class="icon-button" v-bind="$attrs">174<span v-if="icon && iconPosition === 'left'" class="icon">{{ icon }}</span>175<slot />176<span v-if="icon && iconPosition === 'right'" class="icon">{{ icon }}</span>177</button>178</template>179```180181### Link Wrapper Component182183```vue184<script setup>185defineProps({186to: String,187external: Boolean188})189190defineOptions({191inheritAttrs: false192})193</script>194195<template>196<a197v-if="external"198:href="to"199target="_blank"200rel="noopener noreferrer"201v-bind="$attrs"202>203<slot />204</a>205<router-link v-else :to="to" v-bind="$attrs">206<slot />207</router-link>208</template>209```210211## When NOT to Use inheritAttrs: false212213- Simple components with a single semantic root element214- Components where the root element should receive all attributes215- Components that don't wrap other functional elements216217```vue218<!-- SimpleCard.vue - No need for inheritAttrs: false -->219<template>220<article class="card">221<slot />222</article>223</template>224<!-- Passing class, id, or data-* to the root article is fine -->225```226227## References228229- [Fallthrough Attributes - Disabling Attribute Inheritance](https://vuejs.org/guide/components/attrs.html#disabling-attribute-inheritance)230- [Build Advanced Components in Vue 3 using $attrs](https://www.thisdot.co/blog/build-advanced-components-in-vue-3-using-usdattrs)231