Slots is one of the most used features in vue. We get used to use Default Slots and Named Slots to pass html template in components. But slots also has two great features - Dynamic Slot Names and Scoped Slots. That is all this article is about.
Dynamic Slot Names
Official vue documentation says:
Vue docsDynamic directive arguments also work on v-slot, allowing the definition of dynamic slot names:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- with shorthand -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
I found it not that clear and obvious as it supposed to be. So here is a neat example:
Here we have a simple VArticle.vue
component that has 3 named slots - title
, description
, text
to render an article in the correct order and applies the default html styles:
<template>
<h1>
<slot name="title" />
</h1>
<h4>
<slot name="description" />
</h4>
<p>
<slot name="text" />
</p>
</template>
And in the parent component we wonโt declare VArticle.vue
slots explicitly, but create an object with properties representing slots names where the value will be passed and use v-for
and Dynamic directive arguments to resolve the slot:
<script setup>
import VArticle from "./VArticle.vue";
const article = {
title: "Vue slots: advanced level",
description: "This article is about how to use vue slots",
text: "lorem ipsum dolor...",
};
</script>
<template>
<VArticle>
<template v-for="(val, key) of article" :key="key" #[key]>
{{ val }}
</template>
</VArticle>
</template>
Result:
Check it on The Vue SFC Playground
Scoped Slots
Principe
Scoped slots is the mechanism of passing child components data up to the parent component template that will be rendered in the place of the slot. To do this <slot />
default component may accept attributes just like components accept props:
Scoped Slots example
<script setup>
import MyComponent from "./MyComponent.vue";
</script>
<template>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
</template>
<script setup>
const greetingMessage = "Hello World!";
</script>
<template>
<div>
<slot :text="greetingMessage" :count="1" />
</div>
</template>
v-slot
directive makes the slot attributes available on the template. But usually v-slot
used in pair with JS object destructuring like here:
<!-- Using destructuring -->
<template>
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
</template>
This is the most basic example of the scoped slots.
Named Scoped Slots example
It may be also used in pair with the Named Slots:
<script setup>
import MyComponent from "./MyComponent.vue";
</script>
<template>
<MyComponent>
<template #default="{ text, count }">
<p>default slot: {{ text }} {{ count }}</p>
</template>
<template #date="{ date }">
<p>date slot: {{ date }}</p>
</template>
<template #article="{ title, text, views }">
<h1>{{ title }}</h1>
<p>{{ text }}</p>
<span>{{ views }} views</span>
</template>
</MyComponent>
</template>
<script setup>
const greetingMessage = "Hello World!";
const dateString = Date.now();
const someObject = { title: "Title", text: "Lorem ipsum...", views: 12 };
</script>
<template>
<div>
<slot :text="greetingMessage" :count="1" />
<slot name="date" :date="dateString" />
<slot name="article" v-bind="someObject" />
</div>
</template>
#nameSlots name attribute wonโt be included in the props because it is reserved
name=โdefaultโIf you are mixing named slots with the default scoped slot, you need to use an explicit tag for the default slot as in above. Attempting to place the v-slot directive directly on the component will result in a compilation error.Check it on The Vue SFC Playground
Renderless Components
Using Scoped Slots we can make components that only encapsulate logic and do not render anything by themselves - visual output is fully delegated to the consumer component. This example represents Renderless Disclosure.vue component which only handles the show / hide state and the toggle method. The show flag and the toggle method is passed as slot props.
InfoThis approach is used in component libraries like HeadlessUIApp.vue<script setup> import Disclosure from "./Disclosure.vue"; const text = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system."; </script> <template> <Disclosure v-slot="{ show, toggle }"> <div class="disclosure"> <div role="button" class="btn" @click="toggle"> {{ show ? "Show" : "Hide" }} </div> <p :class="{ collapse: show }"> {{ text }} </p> </div> </Disclosure> </template> <style scoped> .disclosure { background-color: pink; border: 4px double black; padding: 10px; } .btn { width: 100%; cursor: pointer; } .collapse { display: none; } </style>
Disclosure.vue<script setup> import { ref } from "vue"; const show = ref(true); function toggle() { show.value = !show.value; } </script> <template> <slot :show="show" :toggle="toggle" /> </template>
The
DIsclosure.vue
component delegates all the visual output to the consumer component via scoped slots so no rendering in it.Check it on The Vue SFC Playground
Conclusion
The main advantage of the Dynamic Slot Names is just that you have this syntax so in some cases it may make your code cleaner and readable.
Scoped Slots and Scoped Named Slots gives a good mechanism to pass data from the slot up to the template where you can use it to build more complex slot rendering logic as in the Renderless Components.