Components and scoped slots in Vue.js

I took me many examples and a lot of practice to understand slots.

Jérémie Litzler
2 min readApr 22, 2024
Credit: Photo by Aarón González on Unsplash.

Whether it’s simple slots, named slots or scoped slots, you will find that it’s a powerful feature in Vue. Let’s dive into it.

The challenge

At the end of last month, I attempted to solve a small project while learning Vue.js concepts.

The project required to build a photo slider that used 2 child components in the App.vue. The SwiperSlide below finds itself in a scoped slot.

The slot resides in the Swiper component as you see with v-slot="{ item }".

<!-- App.vue template -->
<template>
<section>
<Swiper
:list="images"
:index="index"
v-slot="{ item }"
@change="index = $event"
>
<SwiperSlide v-bind="item" />
</Swiper>
</section>
</template>

The challenge that I struggled with was to implement the code so:

  • the App.vue provided the markup (I didn’t have to change App.vue)
  • the SwiperSlide child component would be and didn’t need modification:
1<!-- SwiperSlide.vue template -->
<template>
<img :src="image" :alt="`Image ${title}`" width="400" height="200" />
</template>
<script setup>
defineProps({
image: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
});
</script>

I couldn’t understand how I had to code the Swiper component without touching App.vue and SwiperSlide component.

I read a few times this section of the documentation that described the use case.

How did I make it work

With the following, I completed the task:

<template>
<section class="container">
<Transition>
<ul ref="ulElement">
<li>
<slot :item="{ ...currentImage }"></slot>
</li>
</ul>
</Transition>
<button v-show="showPrev" class="prev-slide" @click="prevImage"><</button>
<button v-show="showNext" class="next-slide" @click="nextImage">></button>
</section>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
const props = defineProps({
list: { type: Array, required: true },
index: { type: Number, required: true },
});
const emits = defineEmits(["@change"]);
const currentImage = computed(() => props.list[props.index]);
const currentImageIndex = computed(() => props.index + 1);
const ulElement = ref(null);
const translate = ref(null);
const showPrev = computed(() => currentImageIndex.value > 1);
const showNext = computed(() => currentImageIndex.value < props.list.length);
const prevImage = () => {
emits("@change", props.index - 1);
translate.value = `-${ulElement.value.offsetWidth}px`;
};
const nextImage = () => {
emits("@change", props.index + 1);
translate.value = `${ulElement.value.offsetWidth}px`;
};
</script>

First, the prop name on the <slot> element is key: it must match what the parent provides to the slot, in our case it is v-slot="{ item }". So the prop must be named :item.

Second, the actual image object must be destructured so that the props definition in SwiperSlide receive them individually.

If I’m mistaken somewhere, tell me so I can correct the wording, but after trying it out, I’m pretty sure that I now understand how scoped slots work.

Thanks for reading this article.

--

--

Jérémie Litzler

I am a software engineer, but not just that: I thrive to improve life around me through observation and awareness about my impact in my environment.