Chapter 55: Vue slot Element
The <slot> element
This is not just another HTML tag — it’s a special placeholder that Vue understands and replaces with content provided by the parent component.
In simple human words:
The child component says: “I have some empty spaces / holes in my design. Parent, you can decide what content goes into those holes.”
The parent then fills those holes with whatever HTML, text, or other components it wants.
This is what allows the same child component to look and behave completely differently depending on who is using it — exactly like real Lego pieces.
Why <slot> exists — the real motivation
Without slots you would have to:
- Pass huge strings via props → ugly & unsafe (v-html)
- Use dozens of boolean / variant props → component explodes with if conditions
- Duplicate almost-identical components → maintenance nightmare
With slots:
- One component can serve many different visual & functional purposes
- Parent controls content & layout
- Child controls structure & styling
- Perfect separation of concerns
Types of Slots in Vue 3 (2026)
- Default / unnamed slot — the main content hole
- Named slots — multiple specific holes with names
- Scoped slots — child passes data back to parent (most powerful)
- Dynamic slot names — rare but useful
Let’s go through them one by one with real, copy-paste-ready examples.
1. Default (Unnamed) Slot — Most Common
Child component: Card.vue (simple wrapper with one main content area)
|
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 33 |
<!-- src/components/Card.vue --> <template> <div class="card"> <div class="card-body"> <!-- This is the default slot — parent content goes here --> <slot /> </div> </div> </template> <script setup lang="ts"> // no props needed for this simple example </script> <style scoped> .card { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.06); background: white; max-width: 500px; margin: 1rem auto; } .card-body { padding: 1.5rem; } </style> |
Parent — different content every time
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!-- Using the same Card component in different ways --> <Card> <p>This is a very simple card with just some text.</p> </Card> <Card> <h3>Product: Vue.js Mastery Course</h3> <p>Price: ₹4,999</p> <button>Buy Now</button> </Card> <Card> <img src="https://example.com/chart.png" alt="Sales chart" style="width:100%;" /> <p>Monthly revenue trend — look how beautiful!</p> </Card> |
→ Same <Card> wrapper — completely different inner content
2. Named Slots — Multiple Specific Places
Child can define several named insertion points.
|
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 |
<!-- Card.vue with named slots --> <template> <div class="card"> <div class="card-header"> <!-- Named slot: header --> <slot name="header"> <!-- fallback if parent doesn't provide header --> <h3>Default Header</h3> </slot> </div> <div class="card-body"> <slot /> <!-- default / unnamed slot --> </div> <div class="card-footer"> <slot name="footer"> <!-- fallback footer --> <small>Default footer</small> </slot> </div> </div> </template> |
Parent usage — filling named slots
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<Card> <!-- Named slots using # shorthand (most common in 2026) --> <template #header> <span class="badge">New!</span> <h3>Premium Subscription</h3> </template> <p>Unlock all courses for ₹999/month</p> <template #footer> <button class="subscribe-btn">Subscribe Now</button> <small>Cancel anytime</small> </template> </Card> |
Shorthand syntax (very popular):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<Card> #header <h3>Custom Header</h3> #footer <small>Footer text</small> Default body content here </Card> |
3. Scoped Slots — Child → Parent Data Passing (The Real Power)
This is where slots become extremely powerful.
The child can pass data back to the parent so the parent can decide how to render that slot using the child’s data.
Child: ProductList.vue (provides product data to parent)
|
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> <ul class="product-list"> <li v-for="product in products" :key="product.id"> <!-- Scoped slot: pass product & helpers back to parent --> <slot name="product" :product="product" :is-new="product.createdAt > Date.now() - 7*24*60*60*1000" :formatPrice="formatPrice" > <!-- fallback if parent doesn't provide slot --> <div>{{ product.name }} — {{ formatPrice(product.price) }}</div> </slot> </li> </ul> </template> <script setup lang="ts"> defineProps<{ products: { id: number; name: string; price: number; createdAt: number }[] }>() function formatPrice(value: number) { return `₹{value.toLocaleString()}` } </script> |
Parent — custom rendering using data from child
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<ProductList :products="products"> <!-- Scoped slot using # shorthand --> <template #product="{ product, isNew, formatPrice }"> <div class="custom-product"> <h4>{{ product.name }}</h4> <p class="price">{{ formatPrice(product.price) }}</p> <span v-if="isNew" class="new-badge">NEW!</span> </div> </template> </ProductList> |
→ Parent decides how each product looks — child only provides the data & loop
Quick Syntax Reference – All Slot Styles (2026)
| Slot Type | Child (defines slot) | Parent (fills slot) – modern shorthand | Parent – full verbose syntax |
|---|---|---|---|
| Default slot | <slot /> | <MyComp>content here</MyComp> | — |
| Named slot | <slot name=”header” /> | <template #header>…</template> | <template v-slot:header>…</template> |
| Scoped slot | <slot :product=”product” /> | <template #product=”{ product }”>…</template> | <template v-slot:product=”slotProps”>…</template> |
| Shorthand (no template tag) | — | <MyComp #header>… #product=”{ product }”>…</MyComp> | — |
Pro Tips from Real Projects (Hyderabad 2026)
- Prefer shorthand # — much cleaner & most codebases use it
- Always provide fallback content inside <slot> — makes component usable without custom content
- Name slots clearly & consistently: header, footer, actions, item, empty, loading, icon, badge
- For UI libraries → almost every reusable component should have at least one slot
- Scoped slots + TypeScript → excellent DX (autocompletion for slot props)
Your mini practice task:
- Create Accordion.vue with default slot for content and named slot for title
- Use it 3 times with different content
- Add a scoped slot for icon that receives isOpen state
Any part confusing? Want to see:
- Scoped slots with TypeScript defineSlots typing?
- Dynamic slot names (#[dynamicSlotName])?
- Slots vs props vs provide/inject comparison?
- Real UI library pattern (Card, Modal, Table with multiple slots)?
Just tell me — we’ll build the next beautiful slotted component together 🚀
