Vue.js offers slots as a powerful tool to help you create reusable components with ease. In this tutorial, we will discuss Vue slots and how to use them to create reusable and flexible components.
We will cover the following topics, along with practical examples of how to apply Vue slots in your applications:
Let’s get started!
In Vue.js, slots are a way to pass content to a component. They allow you to define a section of a component’s template that can be replaced by the parent component. This allows the parent component to control the layout and content of the child component.
Vue slots allow a component to accept dynamic content — known as slot content — and render it in a specific location within the component’s template — known as the slot outlet. This location is specified with the <slot>
element.
The <slot>
element acts as a placeholder for the parent-provided slot content. This solves the problem of having a fixed template in a component, enabling users to add custom content to a Vue component’s layout and making it more flexible and reusable.
Below is an example of how slots work. Make sure you pay attention to the code comments to see where the slot content and slot outlet are.
Consider a <CustomButton>
component that accepts content like this:
<CustomButton> Hello all! <!-- slot content --> </CustomButton>
The template of the <CustomButton>
component could look like this:
<button class="custom-btn"> <slot></slot> <!-- slot outlet --> </button>
The final rendered DOM would look like this:
<button class="custom-btn">Hello all!</button>
As seen in the code above, whatever content is provided in the parent component <CustomButton>
is passed into the slot tags of the child component. The <CustomButton>
component provides the button around the content and the custom styling of the button.
As mentioned earlier, slots are not limited to text — they can also include multiple elements and other components. For example:
<CustomButton> <span style="color:blue">Add</span> <AwesomeIcon name="plus" /> </CustomButton>
Here’s another example of a simple component that uses a slot:
<!-- MyComponent.vue --> <template> <div> <h1>My Component</h1> <slot></slot> </div> </template>
In this example, we have a simple component named MyComponent
that displays a title and a <slot>
element. The <slot>
element is where the parent component can insert content. Here’s an example of how the parent component can use the child component:
<!-- ParentComponent.vue --> <template> <div> <my-component> <p>This is some content that will be inserted into the slot of the child component.</p> </my-component> </div> </template>
In this example, the <p>
element will be inserted into the <slot>
element of the child component, resulting in the following output:
My Component This is some content that will be inserted into the slot of the child component.
The complete code would look something like this:
<!-- MyComponent.vue --> <template> <div> <h1>My Component</h1> <slot></slot> </div> </template> <script> export default { name: 'MyComponent' } </script> <!-- ParentComponent.vue --> <template> <div> <my-component> <p>This is some content that will be inserted into the slot of the child component.</p> </my-component> </div> </template> <script> import MyComponent from './MyComponent.vue'; export default { name: 'ParentComponent', components: { 'my-component': MyComponent } } </script>
Here’s how the final output of the code above would look:
Fallback content refers to the content that is displayed in a slot if there is no content provided for that slot. In Vue, a slot’s fallback content is specified using the default content inside the <slot>
tag.
For example:
<!-- Parent Component --> <template> <div> <header> <slot name="header">Default Header</slot> </header> <main> <slot>Default Main Content</slot> </main> <footer> <slot name="footer">Default Footer</slot> </footer> </div> </template> <!-- Child Component --> <template> <layout> <!-- No header content provided --> <p>Main Content</p> <!-- No footer content provided --> </layout> </template>
In the above example, the parent component has defined fallback content for each slot. If the child component does not provide content for the header or footer slots, the fallback content will be displayed.
Sometimes, you may want to have multiple slots in a single component. To do this, you can use named slots.
Named slots allow you to specify a name for a slot, then use that name in the parent component to indicate which slot the content should be inserted into. Here’s an example of a component that uses multiple named slots:
<template> <div> <h1>My Component</h1> <slot name="header"></slot> <slot name="body"></slot> <slot name="footer"></slot> </div> </template>
In this example, the child component has three named slots: header
, body
, and footer
. The parent component can then use these names to indicate which slot the content should be inserted into, like so:
<template> <div> <my-component> <template v-slot:header> <p>This is the header content</p> </template> <template v-slot:body> <p>This is the body content</p> </template> <template v-slot:footer> <p>This is the footer content</p> </template> </my-component> </div> </template>
In this example, the parent component is using the v-slot
directive to indicate which slot the content should be inserted into. The output should look like the below:
Vue allows you to pass a dynamic value as the name of a slot. This is useful when you want to dynamically decide which slot content to render based on a condition.
To implement dynamic slot names in Vue, you can use the square bracket syntax to bind the slot name to a dynamic value. Here’s an example:
<template> <div> <h1>My Component</h1> <slot :name="currentSlot"></slot> </div> </template> <script> export default { data() { return { currentSlot: 'header' } } } </script> <template> <div> <my-component> <template v-slot:[currentSlot]> <p>This is the {{ currentSlot }} content</p> </template> </my-component> </div> </template>
In this example, the currentSlot
data property is used to bind the name of the slot. In the parent component, the v-slot
directive is used to define the slot content, with the slot name inside square brackets and bound to the currentSlot
data property.
The result of this code would be This is the header content
if the currentSlot
data property is set to header
. If the currentSlot
data property is changed to a different value, such as body
or footer
, the content of the slot would change accordingly.
Let’s look at a more complex example.
Say you have a reusable component, such as a card, that needs to display different content based on the context in which it is used — for example, a Card
component that takes in a title and some content. You could define a default slot for the content and use a static slot name like this:
<template> <div class="card"> <div class="card-header">{{ title }}</div> <div class="card-body"> <slot></slot> </div> </div> </template> <script> export default { props: { title: String } } </script>
However, you may also need to have different types of cards with different structures, such as a card with an image, a card with a list, and so on. To do this, you could use dynamic slot names instead of the default slot:
<template> <div class="card"> <div class="card-header">{{ title }}</div> <div class="card-body"> <slot :name="slotName"></slot> </div> </div> </template> <script> export default { props: { title: String, slotName: { type: String, default: 'default' } } } </script>
Now, when you use the Card
component, you can dynamically assign the slot name based on the type of card you want to display:
<template> <Card title="Card with Image"> <template v-slot:[slotName]> <img src="image.jpg"/> </template> </Card> //if the value passed to 'slotName' above is 'image', an image card is used, //generating the code below: <template> <Card title="Card with Image"> <template v-slot:image> <img src="image.jpg"/> </template> </Card> //and if the value passed to 'slotName' is 'list', a list card is used. <Card title="Card with List"> <template v-slot:list> <ul> <li>Item 1</li> <li>Item 2</li> </ul> </template> </Card> </template>
Scoped slots provide a way to pass data from the child component to the parent component. Instead of passing content to the child component, scoped slots allow the child component to pass data back to the parent component, which can then use that data to render the content in the slot.
Here’s an example of a child component that uses a scoped slot:
<template> <div> <h1>My Component</h1> <slot :item="item"></slot> </div> </template> <script> export default { props: { item: Object } } </script>
In this example, the child component has a single scoped slot that expects to receive an item
object. It also has a props
property that takes an item
object.
The parent component can then use the data passed by the child component to render the content in the slot:
<template> <div> <my-component v-for="item in items" :key="item.id" :item="item"> <template v-slot="{ item }"> <p>{{ item.name }}</p> <p>{{ item.description }}</p> </template> </my-component> </div> </template> <script> export default { data() { return { items: [ { id: 1, name: 'Item 1', description: 'This is the first item' }, { id: 2, name: 'Item 2', description: 'This is the second item' }, { id: 3, name: 'Item 3', description: 'This is the third item' } ] } } } </script>
In the code above, the parent component is using a v-for
loop to iterate over an array of items and passing each item to the child component via the item
prop. The parent component is also using the v-slot
directive to access the data passed by the child component and render the content in the slot.
One of the main benefits of using slots is that they allow you to create highly reusable components. By using slots, the parent component can control the layout and content of the child component. This makes it easy to reuse the child component in multiple places without having to change its code.
However, you can also use slots in Vue in conjunction with functions to create more flexible and reusable code. In this context, we can think of slots as placeholders for functions, where the parent component provides a function to be executed in the child component.
In the child component, we can define a slot that expects a function to be passed as a prop. We can then use this function slot to execute the provided function within the child component. For example:
<template> <div> <slot :myFunction="myFunction"></slot> </div> </template> <script> export default { props: { myFunction: Function } } </script>
In the above example, the child component defines a slot that expects a function called myFunction
to be passed as a prop. The myFunction
prop is then used in the child component’s template to execute the function within the child component.
In the parent component, we can use the v-slot
directive to pass a function to the child component’s function slot:
<template> <div> <child-component> <template v-slot="{ myFunction }"> <button @click="myFunction()">Execute Function</button> </template> </child-component> </div> </template> <script> import ChildComponent from './ChildComponent.vue' export default { components: { ChildComponent }, data() { return { message: 'Hello, World!' } }, methods: { myFunction() { alert(this.message) } } } </script>
In the above code, the parent component passes a function called myFunction
to the child component’s function slot using the v-slot
directive. The child component’s function slot is then destructured to receive the myFunction
prop, which is used in the parent component’s template to execute the function.
By using function slots in this way, we can create more flexible and reusable logic within our Vue components, like using function slots to execute different functions depending on the context in which the component is used.
Renderless components are a powerful technique in Vue that can help us create reusable logic without the overhead of creating a full-blown UI component.
A renderless component is essentially a component that doesn’t render any HTML markup of its own, but instead provides functionality that can be used by other components. By using slots, a renderless component can pass data and functionality to other components, which can then use that data to render the content.
Here’s an example of a renderless component that provides a slot for the parent component to render a list of items:
<template> <slot :items="items"></slot> </template> <script> export default { data() { return { items: [] } }, methods: { addItem(item) { this.items.push(item) }, removeItem(item) { const index = this.items.indexOf(item) if (index !== -1) { this.items.splice(index, 1) } } } } </script>
In the code above, the renderless component doesn’t render any HTML markup of its own, but instead provides a slot that passes an array of items to the parent component. The parent component can then use this slot to render the list of items in any way it sees fit.
Here’s an example of a parent component that uses the renderless component to render a list of items:
<template> <div> <my-list> <template v-slot="{ items }"> <ul> <li v-for="item in items" :key="item">{{ item }}</li> </ul> </template> </my-list> </div> </template> <script> import MyList from './MyList.vue' export default { components: { MyList }, mounted() { this.$refs.list.addItem('Item 1') this.$refs.list.addItem('Item 2') this.$refs.list.addItem('Item 3') this.$refs.list.removeItem('Item 2') } } </script>
Here, the parent component uses the renderless component MyList
to render a list of items. The MyList
component is passed to the v-slot
directive, which destructures the items
prop from the slot and uses it to render an unordered list of items.
The parent component then adds and removes items from the list using the addItem
and removeItem
methods of the MyList
component.
You can use renderless components to encapsulate complex logic and make it reusable across different components in our application. By providing slots for the parent component to use, we can create more flexible and composable components that can be used in a wide range of contexts.
In this tutorial, we covered the basics of Vue slots and how we can use them to create reusable and flexible components. We also discussed how to use multiple and named slots, scoped slots, and slots in renderless components.
By understanding the power of slots, you can take your Vue.js development to the next level and create highly reusable and flexible components.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.