Vue Fallthrough Attributes

Understanding attribute inheritance in components

🎯 What are Fallthrough Attributes?

Fallthrough attributes are HTML attributes or event listeners passed to a component that aren't declared as props or emits. Vue automatically applies them to the component's root element, making components more flexible and reusable without extra configuration.


<!-- MyButton.vue -->
<template>
  <button>
    <slot />
  </button>
</template>

<!-- Parent Usage -->
<template>
  <MyButton class="primary" id="submit-btn" disabled>
    Submit
  </MyButton>
</template>

<!-- Rendered Output -->
<button class="primary" id="submit-btn" disabled>
  Submit
</button>
                                    

Output:

Fallthrough Features

âš¡

Automatic

Attributes pass through automatically

<MyButton class="btn" />
<!-- class applied automatically -->
🎨

Class Merging

Classes combine intelligently

<!-- Both classes apply -->
class="btn primary"
🎧

Event Listeners

Events pass through too

<MyButton @click="handler" />
🚫

Disable Inherit

Control inheritance behavior

defineOptions({
  inheritAttrs: false
})

🔹 Basic Fallthrough Example

Attributes automatically apply to root element:

<!-- CustomInput.vue -->
<template>
  <input type="text" />
</template>

<!-- Parent Usage -->
<template>
  <CustomInput 
    placeholder="Enter name"
    class="form-input"
    maxlength="50"
  />
</template>

<!-- Rendered as -->
<input 
  type="text"
  placeholder="Enter name"
  class="form-input"
  maxlength="50"
/>

Output:

🔹 Class and Style Merging

Classes and styles combine from both sources:

<!-- StyledButton.vue -->
<template>
  <button class="base-btn">
    <slot />
  </button>
</template>

<style scoped>
.base-btn {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
}
</style>

<!-- Parent Usage -->
<template>
  <StyledButton class="primary-btn">
    Click Me
  </StyledButton>
</template>

<!-- Rendered with both classes -->
<button class="base-btn primary-btn">
  Click Me
</button>

Output:

Both base-btn and primary-btn classes applied

🔹 Event Listener Fallthrough

Event handlers pass through automatically:

<!-- ClickableDiv.vue -->
<template>
  <div class="clickable">
    <slot />
  </div>
</template>

<!-- Parent Usage -->
<template>
  <ClickableDiv @click="handleClick" @mouseover="handleHover">
    Hover or Click Me
  </ClickableDiv>
</template>

<script setup>
function handleClick() {
  console.log('Clicked!')
}

function handleHover() {
  console.log('Hovered!')
}
</script>

Output:

Hover or Click Me

Events automatically work on the div

🔹 Disabling Attribute Inheritance

Control where attributes are applied:

<!-- CustomButton.vue -->
<template>
  <div class="button-wrapper">
    <button v-bind="$attrs">
      <slot />
    </button>
  </div>
</template>

<script setup>
defineOptions({
  inheritAttrs: false
})
</script>

<!-- Parent Usage -->
<template>
  <CustomButton class="primary" @click="handleClick">
    Submit
  </CustomButton>
</template>

<!-- Attributes go to button, not wrapper -->
<div class="button-wrapper">
  <button class="primary" @click="handleClick">
    Submit
  </button>
</div>

Output:

Attributes applied to inner button, not wrapper

🔹 Accessing Fallthrough Attributes

Use $attrs to access fallthrough attributes:

<template>
  <div>
    <p>Received attributes:</p>
    <pre>{{ $attrs }}</pre>
    
    <button v-bind="$attrs">
      Button with all attributes
    </button>
  </div>
</template>

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

const attrs = useAttrs()

// Access in script
console.log(attrs.class)
console.log(attrs.onClick)
</script>

🔹 Multiple Root Elements

With multiple roots, specify where attributes go:

<template>
  <header>Header</header>
  <main v-bind="$attrs">
    <!-- Attributes applied here -->
    Main Content
  </main>
  <footer>Footer</footer>
</template>

<script setup>
defineOptions({
  inheritAttrs: false
})
</script>

Output:

Header
Main Content
Footer

Attributes only on main element

🧠 Test Your Knowledge

What happens to fallthrough attributes by default?