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
Table of Contents
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: ` <button> <slot>OK</slot> </button>` }) 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:
<div id="app"> <primary-button></primary-button> </div>
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:
<div id="app"> <primary-button>Subscribe</primary-button> </div>
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: ` <article> <h2>The quote of the day says:</h2> <p class="quote-text"> <slot></slot> </p> </article>` }) app.mount('#app')
<div id="app"> <quote> <div class="quote-box"> "Creativity is just connecting things." <br><br> - Steve Jobs </div> </quote> </div>
.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')
<div id="app"> <quote> <button v-if="show == false" @click="showQuote">Show Quote of the Day</button> <div v-if="show" class="quote-box"> {{ quoteOfTheDay.quote.body }} <br><br> - {{ quoteOfTheDay.quote.author }} </div> </quote> </div>
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: ` <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>` }) app.mount('#app')
<div id="app"> <card> <template v-slot:header> <h2>Card Header Title</h2> </template> <template v-slot:default> <p> Lorem ipsum leo risus, porta ac consectetur ac, vestibulum at eros. Donec id elit non mi porta gravida at eget metus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras mattis consectetur purus sit amet fermentum. </p> </template> <template v-slot:footer> <a href="#">Save</a> - <a href="#">Edit</a> - <a href="#">Delete</a> </template> </card> </div>
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: ` <div class="container"> <div class="card"> <header class="card-header"> <slot name="header"></slot> </header> <main class="card-content"> <slot></slot> </main> <footer class="card-footer"> <slot name="footer"></slot> </footer> </div> </div>` }) app.mount('#app')
.container { width: 300px; }
<div id="app"> <card> <template v-slot:header> <p class="card-header-title"> Card Header Title </p> </template> <template v-slot:default> <p> Lorem ipsum leo risus, porta ac consectetur ac, vestibulum at eros. Donec id elit non mi porta gravida at eget metus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras mattis consectetur purus sit amet fermentum. </p> </template> <template v-slot:footer> <a href="#" class="card-footer-item">Save</a> <a href="#" class="card-footer-item">Edit</a> <a href="#" class="card-footer-item">Delete</a> </template> </card> </div>
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.
Continue reading A Comprehensive Guide to Vue Slots on SitePoint.