A Comprehensive Guide to Vue Slots – SitePoint

Components are the heart of modern web application development. Every app is composed of a number of components smoothly stitched together in order to work as a whole unit. These components need to be maximally flexible and reusable to allow for using them in different situations and even in different apps. One of the main mechanisms many frameworks use to meet such requirements — in partucular Vue — is called a “slot”.
Slots are a powerful and versatile content distribution and composition mechanism. You can think of slots as customizable templates (similar to PHP templates, for example) which you can use in different places, for various use cases, producing different effects. For example, in UI frameworks like Vuetify, slots are used to make generic components such as an alert component. In these kinds of components, slots are used as placeholders for the default content and any additional/optional content, such as icons, images, and so on.
Slots allow you to add any structure, style, and functionality to a particular component. By using slots, developers can drastically reduce the number of props used in a single component, making components much cleaner and manageable.
In this tutorial, we’ll explore how to harness the power of slots in the context of Vue 3. Let’s get started.
Basic Usage of Slots
Basically, Vue offers two kinds of slots: a simple slot, and a scoped slot. Let’s start with the simple one. Consider the following example:
const app = Vue.createApp({})

app.component(‘primary-button’, {
template: `
`
})

app.mount(‘#app’)

Here, we have a primary button component. We want the button’s text to be customizable, so we use the slot component inside the button element to add a placeholder for the text. We also want a default (fallback) generic value in case we don’t provide a custom one. Vue uses as default slot content everything we put inside the slot component. So we just put the text “OK” inside the component. Now we can use the component like this:

See the Pen Vue 3 Slots: Basic Slot by SitePoint (@SitePoint)on CodePen.

The result is a button with text “OK”, because we haven’t provided any value. But what if we want to create a button with custom text? In that case, we provide custom text in the component implementation like this:

Subscribe

Here, Vue takes the custom “Subscribe” text and uses it instead of the default one.
As you can see, even in this simple example, we get a great amount of flexibility over how we want to present our component. But this is only the tip of the iceberg. Let’s look at a more complex example.
Building a Quote of the Day Component
Now, we’ll build a quote component which displays the quote of the day. Here’s the code:
const app = Vue.createApp({})

app.component(‘quote’, {
template: `

The quote of the day says:

`
})

app.mount(‘#app’)

“Creativity is just connecting things.”

– Steve Jobs

.quote-box {
background-color: lightgreen;
width: 300px;
padding: 5px 10px;
}

.quote-text {
font-style: italic;
}

In this example, we create a title heading whose content will be constant, and then we put a slot component inside a paragraph, whose content will vary depending on the current day’s quote. When the component is rendered, Vue will display the title from the quote component followed by the content we put inside the quote tags. Also pay attention to the CSS classes used both in the quote creation and implementation. We can style our components in both ways depending on our needs.

See the Pen Vue 3 Slots: Quote Component by SitePoint (@SitePoint)on CodePen.

Our quote of the day component works fine, but we still need to update the quote manually. Let’s make it dynamic by using the Fav Quotes API:
const app = Vue.createApp({
data() {
return {
quoteOfTheDay: null,
show: false
};
},
methods: {
showQuote() {
axios.get(‘https://favqs.com/api/qotd’).then(result => {
this.quoteOfTheDay = result.data
this.show = true
});
}
}
})

app.mount(‘#app’)


{{ quoteOfTheDay.quote.body }}

– {{ quoteOfTheDay.quote.author }}

Here, we use Axios to make a call to the “Quote of the Day” API endpoint, and then we use the body and author properties, from the returned JSON object, to populate the quote. So we no longer need to add the quote manually; it’s done automatically for us.

See the Pen Vue 3 Slots: Quote Component with Axios by SitePoint (@SitePoint)on CodePen.

Using Multiple Slots
Although a single slot can be quite powerful, in many cases this won’t be enough. In a real-world scenario, we’ll often need more than one single slot to do the job. Fortunately, Vue allows us to use as many slots as we need. Let’s see how we can use multiple slots by building a simple card component.
Building a Basic Card Component
We’ll build a card component with three sections: a header, a body, and a footer:
const app = Vue.createApp({})

app.component(‘card’, {
template: `





`
})

app.mount(‘#app’)



In order to use multiple slots, we must provide a name for each of them. The only exception is the default slot. So, in the above example, we add a name property for the header and footer slots. The slot with no name provided is considered default.
When we use the card component, we need to use the template element with the v-slot directive with the slot name: v-slot:[slot-name].

See the Pen Vue 3 Slots: Card Component by SitePoint (@SitePoint)on CodePen.

Note: the v-slot directive has a shorthand, which uses special symbol # followed by the slot’s name. So, for example, instead of v-slot:header, we can write #header.
Named slots can also be used with third-party components, as we’ll see in the next section.
Using Named Slots with Bulma’s Card Component
Let’s take the Bulma’s Card component and tweak it a little bit:
const app = Vue.createApp({})

app.component(‘card’, {
template: `





`
})

app.mount(‘#app’)

.container {
width: 300px;
}



Here, we use the classes from the Bulma Card component as a base skeleton and add a slot for each section (header, content, footer). Then, when we add the content, everything is structured properly.

See the Pen Vue 3 Slots: Card Component with Bulma by SitePoint (@SitePoint)on CodePen.

Using Scoped Slots
We’ve taken a big step forward by using multiple slots, but the real horse power comes from scoped slots. They allow a parent to get access to the child data, which gives us many opportunities. Let’s see them in practice.
Building a Multipurpose List Component
To demonstrate the power of scoped slots, we’ll build a multipurpose list component that can be used in different scenarios when we need to list some data:
const app = Vue.createApp({
data() {
return {
tasks: [‘Reading a book’, ‘Buying vegetables’, ‘Going for a walk’]
}
}
})

app.component(‘list’, {
props: [‘items’, ‘name’],
template: `

{{ name }}

`
})

app.mount(‘#app’)

{{ task }}

Here, we create a list component with items and name props. The name will be used to give a title to the list and the items will hold the data we want to list. In the template we add a name prop to a heading above the list, and then we use the v-for directive to render each single item. To expose the data from the child to the parent we bind the item as a slot attribute ().
In the parent we have an array of tasks. When we use the list we provide a name prop, then bind the items to the tasks array and we use v-slot to get access to the child data.
Note: all bound props in a slot are called slot props, and we can expose them by using v-slot:[slot-name]=”slotProps”, and then we use single prop like this: {{ slotProps.item }}. But in this example, I use object destructuring, which is more elegant and direct way to get the object properties. Also it allows you to rename the object properties (as I did, renaming item to task), which is more flexible for different kinds of lists.

See the Pen Vue 3 Slots: Multipurpose List Component – Tasks by SitePoint (@SitePoint)on CodePen.

The purpose of our list component is to be used in different scenarios. So, let’s say we want to use it as a shopping list. Here’s how to do it:
const app = Vue.createApp({
data() {
return {
products: [
{name: ‘Tomatoes’, quantity: ‘4’},
{name: ‘Cucumbers’, quantity: ‘2’},
{name: ‘Red onion’, quantity: ‘1’},
]
}
}
})

app.mount(‘#app’)

{{ product.quantity }} {{ product.name }}

Here, we have a list of products where each item has a name and a quantity property. We use the list component almost identically as in the previous example, except that here the list item has two properties.

See the Pen Vue 3 Slots: Multipurpose List Component – Products by SitePoint (@SitePoint)on CodePen.

As you can see, the list component can be easily adapted to different listing use cases.
Slots vs Props
Before learning about slots (and realizing their power), many developers mainly use props for content distribution. But when we have a complex component, the number of props can increase drastically. In such cases, slots can replace the use of props, making component implementation much clearer.
To illustrate the point made above, we’ll take an example from Tailwind CSS, which uses only props to create a vacation-card component:
const app = Vue.createApp({})

app.component(‘vacation-card’, {
props: [“https://www.sitepoint.com/vue-slots-comprehensive-guide/url”, “https://www.sitepoint.com/vue-slots-comprehensive-guide/img”, ‘imgAlt’, ‘eyebrow’, ‘title’, ‘pricing’],
template: `

{{ eyebrow }}
{{ pricing }}

`
})

app.mount(‘#app’)


See the Pen Vue 3 Slots: Tailwind Primer with Props by SitePoint (@SitePoint)on CodePen.

As you can see, to provide all the needed data/content, this vacation-card component uses six props. This definitely makes it reusable, but also hard to maintain. We can produce aa much clearer and maintainable version by using slots instead of some props.
Let’s rewrite the component with slots:
const app = Vue.createApp({})

app.component(‘vacation-card’, {
props: [“https://www.sitepoint.com/vue-slots-comprehensive-guide/url”, “https://www.sitepoint.com/vue-slots-comprehensive-guide/img”, ‘imgAlt’],
template: `

`
})

app.mount(‘#app’)





See the Pen Vue 3 Slots: Tailwind Primer with Slots by SitePoint (@SitePoint)on CodePen.

Here, we reduce the props to three. We leave props only for the metadata and use named slots for the actual content, which I think is way more logical.
Here are my general considerations about using props vs slots.
Use props when:
the parent can only pass the data down to the child component
the parent has no control over how the data will be rendered and can’t customize the child component
you have a defined design, the number of variables is small, and the component is simple
you need to provide metadata or some sort of configuration
Use slots when:
you need to pass data from the child to the parent
the parent can determine how the data will be rendered
you want to pass not only data but also complex HTML markup, a particular functionality, and even other components
you want the versatility to customize the child component from the parent
you need more flexibility to customize the component, the component is large, and there are many variables involved
The bottom line: for best results, combine props and slots! 😀
Exploring More Slots Use Cases
At this stage, you should see the power and flexibility of slots, but there are even more useful ways to use them. Let’s explore them now.
Reusing Functionality with Slots
Slots can deliver not only content/structure but also functionality. Let’s see this in action:
const app = Vue.createApp({
data() {
return {
counter: 1
}
},
methods: {
increment() {
this.counter++
}
}
})

app.component(‘double-counter’, {
props: [‘counter’],
template: `

`,
computed: {
double() {
return this.counter * 2
}
}
})

app.mount(‘#app’)



Here, we create a double-counter component that will double the value of a counter. To do this, we create a computed property double which we expose to the parent by binding its value as a slot attribute. The computed takes the value of the counter prop and doubles it.
In the parent we have a counter data property and an increment() method that increments it by one. When we use the double-counter, we bind the prop counter, then we expose the double computed property. In the expression, we use the counter and double. When we click the button, the counter is incremented by 1 and the computed property is recalculated with the new doubled value. For example, if the counter prop is set to 3, the doubled value will be 6 (3 x 2 = 6).

See the Pen Vue 3 Slots: Reusing Functionality – Double Counter by SitePoint (@SitePoint)on CodePen.

We can make this component more flexible. Let’s tweak it to multiply the counter by any custom value:
const app = Vue.createApp({
data() {
return {
counter: 1,
by: 4
}
},
methods: {
increment() {
this.counter++
}
}
})

app.component(‘multiply-counter’, {
props: [‘counter’, ‘by’],
template: `

`,
computed: {
multiply() {
return this.counter * this.by
}
}
})

app.mount(‘#app’)



Here, we add a prop, by, which sets the multiplication number, and we change the double computed property to multiply, which multiplies the counter by the given number.
In the parent, we add the by data property and bind it to the by prop. So now, if we set the by data property to 3 and the counter is 4, the result will be 4 x 3 = 12.

See the Pen Vue 3 Slots: Reusing Functionality – Multiply Counter by SitePoint (@SitePoint)on CodePen.

Using Slots in Renderless Components
The other powerful way to use slots is to put them in a renderless component. A renderless component has no template element. It has a render function that exposes a single scoped slot. Let’s create a renderless version of our multipurpose list:
const app = Vue.createApp({
data() {
return {
products: [
{name: ‘Tomatoes’, quantity: ‘4’},
{name: ‘Cucumbers’, quantity: ‘2’},
{name: ‘Red onion’, quantity: ‘1’},
]
}
}
})

app.component(‘renderless-list’, {
props: [‘items’, ‘name’],
render() {
return this.$slots.default({
items: this.items,
name: this.name
});
}
})

app.mount(‘#app’)



Here, we create a renderless-list component which takes name and items props. It also exposes a single scoped slot in the render function.
Then, in the parent we use it in a similar way as our multipurpose list component — except that, this time, the content structure is defined in the parent, which gives us more flexibility as we’ll see in the next example.

See the Pen Vue 3 Slots: Renderless Multipurpose Component – List by SitePoint (@SitePoint)on CodePen.

Note: in Vue 3, this.$scopedSlots is removed and this.$slots is used instead. Also, this.$slots exposes slots as functions. See Slot Unification for more information.
The real power of this component is that we’re not restricted how will render the content. Let’s see how easy is to render the same content as a table:



table, th, td {
border: 1px solid black;
border-collapse: collapse;
padding: 5px;
}

Here, we use the same functionality but we define different content structure.

See the Pen Vue 3 Slots: Renderless Multipurpose Component – Table by SitePoint (@SitePoint)on CodePen.

Conclusion
So this is all the power and flexibility of slots. As we’ve seen, they can be used in a variety of use cases, which allows us to produce highly reusable and versatile components. When combined with props, slots give us all we need to create complex components and apps. Slots are easy to use but extremely powerful. If you haven’t used them before, I think now is the perfect time to start doing so. The best place to start or continue this journey is the official Slots Documentation. Good luck! 😎

Coded at

Share your love

Leave a Reply