How to Specify Where Non-Prop Attributes Should Be Used

May 17 2018 | By Taha Shashtari

If you pass an HTML attribute to a component without having a prop defined for it, Vue will either replace it or merge it with existing attributes.

So, let's say we have this custom input component (let's name it BaseTextField.vue):

<template>
  <input
    type="text"
    class="from-base-text-field"
    placeholder="BaseTextField placeholder"
  >
</template>

<script>
export default {
  // No props defined
}
</script>

And we're using it from a component called App.vue:

<template>
  <div class="app">
    <base-text-field
      class="from-app"
      placeholder="App placeholder"
    />
  </div>
</template>

Now if you run this in the browser, you should see that the class of the displayed <input> element is "from-base-text-field from-app", and the placeholder is "App placeholder".

So for the class case, the values from App.vue and BaseTextField were merged. But for the placeholder, the value passed from App.vue replaced the value set by BaseTextField component.

So, when passing attributes that don't have corresponding props defined, most attributes will be replaced by the passed values except for class and style which will be merged instead.

But how to use these attributes somewhere else?

When passing non-prop attributes, the root element of the component will be affected by them. But what if we want to use these attributes on another element?

So, for example, let's see how we can use these attributes on the input element if we modified the component to something like this:

<template>
  <div>
    <label>Your Label</label>
    <input
      type="text"
      class="from-base-text-field"
    >
  </div>
</template>

<script>
export default {
  // No props defined
}
</script>

If you run this example, you would notice that the passed placeholder and class are used on the root <div> element — that's expected. Now if we want to use them on the <input> element instead, we should add v-bind="$attrs" to it, like this:

<input
  v-bind="$attrs"
  type="text"
  class="from-base-text-field"
>

Since $attrs contains all non-prop attributes except class and style, you should only see the placeholder used on the <input>. The class would still be used on the root element.

If you check the browser now, you would see that the root element is still using the placeholder value. But we should disable this behavior. We can do this by adding inheritAttrs: false to your component's options.

So, the final code should look like this:

<template>
  <div>
    <label>Your Label</label>
    <input
      v-bind="$attrs"
      type="text"
      class="from-base-text-field"
    >
  </div>
</template>

<script>
export default {
  inheritAttrs: false
}
</script>