Vue Slots
In some scenarios, we may need to pass template content from a parent component to a child component. For example, a generic card component where we want its main content to be customizable. In such cases, we need to use Slots.
Slots are a powerful feature of Vue components that allow you to compose components in a predefined way, "inserting" content from the parent component into specific locations in the child component.
Default Slot
The simplest form of slots is a single, default slot. The child component can use the <slot> tag to create a "content outlet" for itself.
FancyButton.vue (Child Component)
<template>
<button class="fancy-btn">
<slot></slot> <!-- Slot outlet -->
</button>
</template>
<style>
.fancy-btn {
background: linear-gradient(315deg, #42d392 25%, #647eff);
border: none;
padding: 8px 16px;
border-radius: 8px;
color: white;
cursor: pointer;
}
</style>When the parent component uses FancyButton, it can pass any template content inside it. This content will be rendered at the location of the <slot> tag in the child component.
App.vue (Parent Component)
<script setup>
import FancyButton from './FancyButton.vue'
</script>
<template>
<FancyButton>
Click me! <!-- Slot content -->
</FancyButton>
</template>The rendered HTML will be:
<button class="fancy-btn">
Click me!
</button>Named Slots
Sometimes we need multiple slots. For example, a layout component with a header, main content, and footer. For this, the <slot> element has a special attribute name that can be used to assign unique IDs to different slots.
BaseLayout.vue (Child Component)
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- Default slot -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>To provide content to named slots in the parent component, we need to use a <template> element with the v-slot directive, passing the slot name as the parameter to v-slot.
App.vue (Parent Component)
<script setup>
import BaseLayout from './BaseLayout.vue'
</script>
<template>
<BaseLayout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
</template>v-slot has a dedicated shorthand syntax #, so <template v-slot:header> can be abbreviated to <template #header>.
Scoped Slots
Sometimes, it's useful to allow slot content to access data that only exists in the child component. For example, a list component where we want the parent component to customize the rendering of each item.
To achieve this, we can pass attributes to the slot, similar to how we do with props.
MyComponent.vue (Child Component)
<script setup>
import { ref } from 'vue'
const items = ref(['Feed a cat', 'Buy milk'])
</script>
<template>
<ul>
<li v-for="(item, index) in items">
<slot :item-text="item" :index="index"></slot>
</li>
</ul>
</template>Now, in the parent scope, we can use v-slot with a value to define the name of the slot props we provide.
App.vue (Parent Component)
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent v-slot="slotProps">
<i>{{ slotProps.itemText }} (index: {{ slotProps.index }})</i>
</MyComponent>
</template>In this example, we chose to name the object containing all slot props slotProps, but you can use any name you like. You can also use destructuring to get specific props:
<MyComponent v-slot="{ itemText, index }">
<i>{{ itemText }} (index: {{ index }})</i>
</MyComponent>Scoped slots allow us to create highly flexible and reusable components, encapsulating data logic in the child component while giving the parent component control over the visual presentation.