Chapter 65: Vue $slots Object
The $slots object
This is not something you write or create yourself — Vue automatically gives it to every component instance.
$slots is basically a read-only map that tells the current component:
“Hey, the parent who used me passed me some content to insert at these named places (slots). Here is everything they gave me — go ahead and render it wherever you want.”
In modern Vue 3 + <script setup>, you almost never write $slots directly — instead you use the <slot> tag in the template, and Vue handles the rest behind the scenes.
But understanding $slots deeply will make you much better at:
- Building reusable UI components (cards, modals, layouts, tables…)
- Debugging slot-related bugs
- Creating advanced scoped/named slots
- Writing custom slot utilities or renderless components
1. Mental Model – What $slots Really Is
When a parent uses your component like this:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<MyCard> <!-- default/unnamed slot --> <p>Main body content</p> <!-- named slots --> <template #header> <h3>Custom Header</h3> </template> <template #footer> <button>Action</button> </template> <!-- scoped slot --> <template #item="{ product }"> <div>{{ product.name }}</div> </template> </MyCard> |
Vue internally creates an object on the child instance that looks something like this:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
// Simplified internal representation $slots = { default: [VNode { children: [text "Main body content"] }], header: [VNode { children: [h3 "Custom Header"] }], footer: [VNode { children: [button "Action"] }], item: [VNode { scopeId: "...", children: [...] }] // scoped slot function } |
- Each key = slot name (default, header, footer, item…)
- Each value = array of virtual nodes (VNodes) that the parent passed for that slot
2. How You Actually Use Slots (Modern 2026 Way)
You almost never write $slots directly — Vue handles it when you use <slot> tags.
Child component – defining slots
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<!-- MyCard.vue --> <template> <div class="card"> <!-- Named slot with fallback --> <div class="card-header"> <slot name="header"> <h3>Default Header</h3> </slot> </div> <!-- Default (unnamed) slot --> <div class="card-body"> <slot /> </div> <!-- Named slot with fallback --> <div class="card-footer"> <slot name="footer"> <small>Default footer</small> </slot> </div> </div> </template> <script setup lang="ts"> // No code needed — <slot> tags do all the work </script> |
Parent – filling slots
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<MyCard> <!-- Named slots – modern # shorthand (most common in 2026) --> <template #header> <span class="badge">New!</span> <h3>Premium Plan</h3> </template> <!-- Default slot – just normal content --> <p>Unlock all features for ₹999/month</p> <template #footer> <button class="subscribe">Subscribe Now</button> <small>Cancel anytime</small> </template> </MyCard> |
3. Checking If a Slot Exists (Very Useful in Reusable Components)
Sometimes you want to conditionally render something only if parent provided a certain slot.
Child – check $slots
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<template> <div class="card"> <!-- Show header only if parent gave us header slot --> <div v-if="$slots.header" class="card-header"> <slot name="header" /> </div> <div class="card-body"> <slot /> </div> <!-- Show footer only if provided --> <div v-if="$slots.footer" class="card-footer"> <slot name="footer" /> </div> <!-- Show default "no footer" message if no footer slot --> <div v-else class="card-footer-empty"> No footer provided </div> </div> </template> <script setup lang="ts"> // $slots is automatically injected – no import needed </script> |
4. Scoped Slots – The Real Power (Child → Parent Data Passing)
Child can pass data back to parent for that slot.
Child – providing data
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!-- ProductList.vue --> <template> <ul> <li v-for="product in products" :key="product.id"> <!-- Scoped slot: pass product & helpers --> <slot name="product" :product="product" :is-new="product.createdAt > Date.now() - 7*24*60*60*1000" :format-price="formatPrice" > <!-- fallback --> <div>{{ product.name }} — ₹{{ formatPrice(product.price) }}</div> </slot> </li> </ul> </template> <script setup lang="ts"> defineProps<{ products: any[] }>(); function formatPrice(value: number) { return value.toLocaleString('en-IN'); } </script> |
Parent – using scoped slot data
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<ProductList :products="products"> <template #product="{ product, isNew, formatPrice }"> <div class="custom-product"> <h4>{{ product.name }}</h4> <p>{{ formatPrice(product.price) }}</p> <span v-if="isNew" class="new-badge">NEW!</span> </div> </template> </ProductList> |
5. Quick Summary Table – $slots in 2026
| Question | Answer / Behavior |
|---|---|
| Do I import $slots? | No — Vue injects it automatically in every component |
| Is $slots reactive? | Yes — changes when parent re-renders with different slot content |
| What does $slots contain? | Object where keys = slot names, values = array of VNodes |
| Default slot key? | ‘default’ |
| How to check if slot exists? | if ($slots.header) or if ($slots[‘header’]) |
| Scoped slot? | Parent receives data via destructuring: #product=”{ product }” |
| Fallback content? | Put it inside <slot> — shown only if parent doesn’t provide slot |
| Named slot shorthand? | <template #header> (modern & most common) |
Pro Tips from Real Projects (Hyderabad 2026)
- Always provide fallback content inside <slot> — makes component usable without custom slots
- Use named slots for complex layouts (header, footer, actions, item, empty, loading…)
- Prefer scoped slots when child has data parent might want to customize rendering with
- Use $slots checks (v-if=”$slots.header”) to conditionally render wrappers or fallbacks
- In UI libraries → almost every component should have thoughtful default slots + named slots + scoped slots
- Scoped slots + TypeScript → excellent DX (autocompletion for slot props)
Your mini practice task:
- Create Card.vue with named slots: header, body (default), footer
- Use it twice — once with only default slot, once with all named slots
- Add v-if=”$slots.footer” → show different footer styling when footer is provided
- Add scoped slot #actions that receives isLoading prop from child
Any part confusing? Want full examples for:
- Scoped slots with TypeScript defineSlots typing?
- Dynamic slot names (#[dynamicSlotName])?
- Real UI library pattern (Card, Modal, Table with multiple slots)?
- Slots vs props vs provide/inject comparison?
Just tell me — we’ll build the next beautiful slotted component together 🚀
