Vue Scoped Slots
Pass data from child to parent through slot content
🔄 What are Scoped Slots?
Scoped slots let child components pass data back to the parent through slot content. This powerful pattern enables the parent to control rendering while accessing child component data, creating flexible and reusable components.
<!-- Child passes data -->
<slot :user="user" :index="i"></slot>
<!-- Parent receives data -->
<template #default="{ user, index }">
<p>{{ user.name }}</p>
</template>
Scoped Slot Benefits
Data Sharing
Child shares data with parent
<slot :item="item">
</slot>
Custom Rendering
Parent controls how data displays
<template #default="{ item }">
<div>{{ item }}</div>
</template>
Reusability
Same logic, different presentations
<List>
<template #default="{ item }">
<!-- Custom view -->
</template>
</List>
Flexibility
Powerful component patterns
<template #item="props">
{{ props.data }}
</template>
🔹 Basic Scoped Slot
Create a list component that passes item data to parent:
🔸 Child Component (ItemList.vue)
<template>
<div class="list">
<div v-for="(item, index) in items" :key="index">
<slot :item="item" :index="index"></slot>
</div>
</div>
</template>
<script setup>
defineProps({
items: Array
})
</script>
🔸 Parent Component
<template>
<ItemList :items="fruits">
<template #default="{ item, index }">
<p>{{ index + 1 }}. {{ item }}</p>
</template>
</ItemList>
</template>
<script setup>
import { ref } from 'vue'
const fruits = ref(['Apple', 'Banana', 'Orange'])
</script>
Output:
1. Apple
2. Banana
3. Orange
🔹 User List with Scoped Slots
Build a flexible user list component:
<!-- Child Component (UserList.vue) -->
<template>
<div class="user-list">
<div v-for="user in users" :key="user.id" class="user-item">
<slot :user="user" :isOnline="user.online">
<!-- Default fallback -->
<p>{{ user.name }}</p>
</slot>
</div>
</div>
</template>
<!-- Parent Usage -->
<template>
<UserList :users="userList">
<template #default="{ user, isOnline }">
<div>
<h3>{{ user.name }}</h3>
<span :style="{ color: isOnline ? 'green' : 'gray' }">
{{ isOnline ? 'Online' : 'Offline' }}
</span>
</div>
</template>
</UserList>
</template>
🔹 Table with Scoped Slots
Create a reusable table with custom cell rendering:
<!-- Child Component (DataTable.vue) -->
<template>
<table>
<thead>
<tr>
<th v-for="col in columns" :key="col">{{ col }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in data" :key="index">
<td>
<slot :row="row" :index="index"></slot>
</td>
</tr>
</tbody>
</table>
</template>
<!-- Parent Usage -->
<template>
<DataTable :data="products" :columns="['Name', 'Price']">
<template #default="{ row }">
<td>{{ row.name }}</td>
<td>${{ row.price.toFixed(2) }}</td>
</template>
</DataTable>
</template>
🔹 Passing Methods Through Scoped Slots
Share functions from child to parent:
<!-- Child Component (TodoList.vue) -->
<template>
<div>
<div v-for="todo in todos" :key="todo.id">
<slot
:todo="todo"
:toggle="() => toggleTodo(todo.id)"
:remove="() => removeTodo(todo.id)"
></slot>
</div>
</div>
</template>
<script setup>
const toggleTodo = (id) => { /* logic */ }
const removeTodo = (id) => { /* logic */ }
</script>
<!-- Parent Usage -->
<template>
<TodoList>
<template #default="{ todo, toggle, remove }">
<div>
<input type="checkbox" @change="toggle" />
<span>{{ todo.text }}</span>
<button @click="remove">Delete</button>
</div>
</template>
</TodoList>
</template>
🔹 Multiple Scoped Slots
Use named scoped slots for different sections:
<!-- Child Component (Card.vue) -->
<template>
<div class="card">
<header>
<slot name="header" :title="title" :date="date"></slot>
</header>
<main>
<slot name="content" :body="body"></slot>
</main>
<footer>
<slot name="footer" :author="author"></slot>
</footer>
</div>
</template>
<!-- Parent Usage -->
<template>
<Card>
<template #header="{ title, date }">
<h2>{{ title }}</h2>
<small>{{ date }}</small>
</template>
<template #content="{ body }">
<p>{{ body }}</p>
</template>
<template #footer="{ author }">
<span>By {{ author }}</span>
</template>
</Card>
</template>
🔹 Destructuring with Default Values
Provide fallback values when destructuring:
<template>
<ProductList>
<template #default="{ product, discount = 0, inStock = true }">
<div>
<h3>{{ product.name }}</h3>
<p>Price: ${{ product.price * (1 - discount) }}</p>
<span v-if="inStock">Available</span>
<span v-else>Out of Stock</span>
</div>
</template>
</ProductList>
</template>
Tip: Use default values in destructuring to handle optional slot props gracefully.