In Vue.js, a slot is a mechanism that allows you to inject an HTML fragment into a component, from another parent component.
That is, it’s as if the child component had a “hole”, where it lets others pass it HTML content, to display it inside itself.
This way we can reuse our component, which acts as a “picture frame”, for content that can be variable.
For example, imagine you have a component that renders a very elaborate card. Another component can pass it the content it should display inside the card.
Slots are an alternative to Props, when we don’t just need to change certain values, but the entire content of the component.
What are slots?
The default slot is the most basic and common. It allows us to insert a single piece of content into a single place within the child component.
In the child component, the <slot> tag acts as a “placeholder” that will be replaced by the inserted content.
<template>
<div class="card">
<slot></slot> <!-- Default slot -->
</div>
</template>
<style>
.card {
border: 1px solid #ccc;
padding: 20px;
border-radius: 8px;
}
</style>
<template>
<CardComponent>
<p>This is the content of the card.</p>
</CardComponent>
</template>
<script setup>
import CardComponent from './CardComponent.vue';
</script>
In this example:
- The child component (
CardComponent) defines a default slot. - The parent component (
ParentComponent) inserts content inside the slot.
Types of Slots
Named Slots
Named slots are a mechanism that allows a component to offer multiple slots. To do this, we will give each slot a unique name (to be able to reference it).
<template>
<div class="layout">
<header>
<slot name="header"></slot> <!-- Slot named "header" -->
</header>
<main>
<slot></slot> <!-- Default slot -->
</main>
<footer>
<slot name="footer"></slot> <!-- Slot named "footer" -->
</footer>
</div>
</template>
<style>
.layout {
border: 1px solid #ccc;
padding: 20px;
border-radius: 8px;
}
</style>
<template>
<LayoutComponent>
<template v-slot:header>
<h1>Custom Header</h1>
</template>
<p>This is the main content.</p>
<template v-slot:footer>
<p>Custom Footer</p>
</template>
</LayoutComponent>
</template>
<script setup>
import LayoutComponent from './LayoutComponent.vue';
</script>
In this example:
- The child component (
LayoutComponent) defines three slots:header,footer, and a default slot. - The parent component (
ParentComponent) inserts content into each slot usingv-slot.
Scoped Slots
Scoped slots allow passing data from the child component to the parent component. This is useful when the HTML fragment we are inserting in turn needs to access the content of the component that is going to insert it into the slot.
<template>
<ul>
<li v-for="(item, index) in items" :key="index">
<slot :element="item" :index="index"></slot>
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const items = ref(['Apple', 'Banana', 'Cherry', 'Peach'])
</script>
<template>
<List>
<template v-slot:default="{ element, index }">
<strong>{{ index + 1 }}:</strong> {{ element.toUpperCase() }}
</template>
</List>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
</script>
In this example:
- The child component (ChildComponent.vue) creates a list of elements and exposes each one through the slot using
:elementand:index. - The parent component (App.vue) uses v-slot to receive that data and customize the content of each element.
