Slots in Vuejs

Slots in Vue lets you pass mark up or templates as children to components. Simply put, they let users insert templates into specific parts of another component.

For example, say we have a custom AppButton component which is just a button for submitting a form, and we want this button to have dynamic text content depending on where it is used, we could receive the dynamic text content using slots.

// AppButton.vue
<button type="submit">
   <slot></slot>
</button>

When the AppButton component is used, the template that is passed into it would replace the slot tag in it.

// Signup.vue
<form>
   ***form content***
   <AppButton>Register</AppButton>
</form>

This then means that the AppButton can be used in different forms and the button text would be rendered dynamically based on the template it receives.

Default content

In as much as we want the button text in the AppButton component to be rendered dynamically (i.e. passed from the component where it is used), we may also want to set a fallback text for cases where we don't pass a button text. We achieve this by adding the fallback text as the content of the slot tag in the AppButton component like so:

// AppButton.vue
<button type="submit">
   <slot>Submit</slot>
</button>

Now whenever we don't pass any content to the AppButton, the text displayed would be "Submit". It's important to note that the slot in the AppButton component is called a default slot.

Named Slots

Say we want to have multiple slots in a component so users of this component can insert templates into different parts, this is where named slots come in. With named slots, users can specify where exactly to insert templates into. Say we have a reusable component called CommentItem which is used to render a single comment of a post:

//CommentItem.vue
  <div>
    <img :src="props.img" />
    <div>
      <slot name="username"></slot>
      <slot name="comment"></slot>
      <slot></slot>
    </div>
  </div>

We can see that the first two slots above have a name property, this gives users of this component the ability to insert templates in those slots by specifically targeting them using the name assigned to them like so:

<CommentItem :img=`img_url`>
   <template v-slot:username> @klevamane </template>
   <template v-slot:comment> Looking forward to this! </template>
   <template> Location: Lagos, Nigeria. </template>
</CommentItem>

The code snippet above shows how the CommentItem component would be used. The v-slot directive is used to target specific named slots.

Notice how the third template doesn't have doesn't use the v-slot directive, this is because it targets the default(nameless) slot defined in CommentItem so there is no need for a name to be set. It could also have the v-slot directive but with default being the name of the slot. This is because a slot without a name implicitly has the name default.

Scoped Slots

Scoped slots allows you to pass data or functions (slotProps) up from child components to their parents so they can be modified there.

Say we have a ProductList component, that loops through an array of products and renders each item like so:

//ProductList.vue
  <template>
    <div>
      <div :key="product.id" v-for="product in products">
        <img :src="product.img" />
        <p>{{ product.name }}</p>
      </div>
    </div>
  </template>

If we then want to use this component in a page, all we need to do is include it in the template. But what if in some other page, we don't want to show the image and at the same time, we want to capitalize the product name and hide the add to cart button. Actually, we can achieve this by passing different props to the ProductList component that tells it when to show the image or capitalize the product name like so:

<ProductList :showImage="false" :capitalizeName="true" :showCartButton="false" />

But things would get a bit messy when we have a lot more things to tweak in the ProductList component, as it would now be filled with a lot of v-ifs, v-shows and conditional stylings, etc. Scoped slots comes to our rescue here! With scoped slot, we can bind any data/function as attributes to the slot element, and automagically, this props would be passed up to the parent.

Element bound to the slot element are called slot-props.

    <template>
      <ul>
        <li :key="product.id" v-for="product in products">
          <slot :product="product">
            <img :src="product.img" />
            {{ product.name }}
          </slot>
        </li>
      </ul>
    </template>

Two major things to note here:

  • The product prop would now be available to the parent component i.e. where the ProductList component is used.
  • There is a default slot with a fallback content so when we don't pass any content into the slot from the parent, the fallback content gets rendered.

So the ProductList would be used this way in a page:

<ProductList v-slot:default="slotProps">
   {{slotProps.product.name.toUpperCase()}}
</ProductList>

Now we have access to slotProps which is an object containing all the props sent from the slot(child).

Notice the v-slot directive being placed directly in the ProductList tag, this is because there is only one slot in the ProductList component and this slot doesn't have a name(default slot). This can be further shortened.

<ProductList v-slot="slotProps">
   {{slotProps.product.name.toUpperCase()}}
</ProductList>

The Vue docs explains it this way:

Just as non-specified content is assumed to be for the default slot, v-slot without an argument is assumed to refer to the default slot.

Conclusion

Slots in Vue are really great for implementing flexibility and composability in components. This article explains slots and shows different use cases where they can be applied. There are even a lot more use cases, and I hope you explore them as you build your Vue apps!