Chapter 84: Vue v-slot Directive
V-slot directive (often written with its beautiful shorthand #)
This is the modern, official way (since Vue 2.6 / Vue 3) to work with slots — replacing the older slot=”name” and slot-scope syntax.
v-slot is what allows a parent component to inject custom content (HTML, text, other components) into specific named places inside a child component — and in the case of scoped slots, it also lets the child pass data back to the parent so the parent can decide how to render that content.
In simple human words:
The child says: “I have some holes here — header, footer, item, actions… you can put whatever you want in them.” The parent says: “Great — here’s my content for each hole. And if you give me some data about what goes in the hole, I’ll decide how to style/render it.”
Why v-slot / # is so important (real motivation)
Without slots (and v-slot), components would be very rigid:
- You’d have to pass huge props (strings, objects, render functions) → ugly
- Or use dozens of boolean/variant props → component explodes
- Or duplicate almost-identical components → maintenance hell
With v-slot:
- Same child component can look completely different depending on parent
- Parent controls content & layout
- Child controls structure & styling
- Perfect separation of concerns
Types of Slots & v-slot Syntax (2026 Style)
| Slot Type | Child (defines) | Parent (fills) – 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 :item=”item” /> | <template #item=”{ item }”>…</template> | <template v-slot:item=”slotProps”>…</template> |
| Shorthand (no template tag) | — | <MyComp #header>… #item=”{ item }”>…</MyComp> | — |
Real, Complete Example – Modern Card with Named & Scoped Slots
Child: FancyCard.vue (reusable card with multiple 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
<!-- src/components/FancyCard.vue --> <template> <div class="fancy-card"> <!-- Named slot: header with fallback --> <div class="card-header"> <slot name="header"> <h3>Default Title</h3> </slot> </div> <!-- Default (unnamed) slot – main content --> <div class="card-body"> <slot /> </div> <!-- Named slot: actions --> <div class="card-actions"> <slot name="actions"> <button>Default Action</button> </slot> </div> <!-- Scoped slot: item – passes data back to parent --> <div class="card-items"> <div v-for="item in items" :key="item.id"> <slot name="item" :item="item" :index="items.indexOf(item)" :is-first="items.indexOf(item) === 0" > <!-- fallback rendering if parent doesn't use slot --> <div>{{ item.name }} (index {{ items.indexOf(item) }})</div> </slot> </div> </div> </div> </template> <script setup lang="ts"> defineProps<{ items: { id: number; name: string }[] }>() </script> <style scoped> .fancy-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: 2rem auto; } .card-header { background: #f8fafc; padding: 1.2rem 1.5rem; border-bottom: 1px solid #e5e7eb; } .card-body { padding: 1.5rem; } .card-actions { padding: 1rem 1.5rem; border-top: 1px solid #e5e7eb; text-align: right; } .card-items { padding: 0 1.5rem 1.5rem; } </style> |
Parent – different usage patterns
|
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
<template> <div> <!-- 1. Only default slot --> <FancyCard> <p>This card uses only the main body slot.</p> </FancyCard> <!-- 2. All named slots + scoped slot --> <FancyCard :items="products"> <template #header> <span class="badge">Featured</span> <h3>Our Top Products</h3> </template> <p>Check out these amazing items below:</p> <!-- Scoped slot using shorthand # --> <template #item="{ item, index, isFirst }"> <div class="custom-item" :class="{ 'first-item': isFirst }"> <strong>#{{ index + 1 }}</strong> {{ item.name }} </div> </template> <template #actions> <button class="view-all">View All Products</button> </template> </FancyCard> <!-- 3. Shorthand syntax without <template> tags (very clean) --> <FancyCard> #header <h3>Quick Summary</h3> #actions <small>Updated 5 min ago</small> Default body content here </FancyCard> </div> </template> <script setup lang="ts"> const products = [ { id: 1, name: 'Vue Course' }, { id: 2, name: 'TypeScript Kit' }, { id: 3, name: 'Tailwind Bundle' } ] </script> <style scoped> .badge { background: #fbbf24; color: #92400e; padding: 0.2rem 0.6rem; border-radius: 9999px; font-size: 0.8rem; } .custom-item { padding: 0.8rem; background: #f8fafc; border-radius: 6px; margin: 0.5rem 0; } .first-item { border-left: 4px solid #3b82f6; padding-left: 1rem; } .view-all { background: #22c55e; color: white; padding: 0.6rem 1.2rem; border: none; border-radius: 6px; cursor: pointer; } </style> |
Quick Syntax Reference – v-slot / # in 2026
| Slot Type | Child (defines) | Parent (fills) – recommended 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 :item=”item” /> | <template #item=”{ item }”>…</template> | <template v-slot:item=”slotProps”>…</template> |
| Shorthand (no template tag) | — | <MyComp #header>… #item=”{ item }”>…</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 even without custom slots
- Name slots clearly & consistently: header, footer, actions, item, empty, loading, icon, badge…
- For scoped slots → child should pass useful helpers (:index, :is-first, :formatPrice, :remove)
- In UI libraries → almost every component should have thoughtful default slot + named slots + scoped slots
- Scoped slots + TypeScript → excellent DX (autocompletion for slot props)
Your mini practice task:
- Create Modal.vue with named slots: header, body (default), footer
- Use it twice: once with simple text, once with form inside body
- Add scoped slot for footer that receives isSubmitting state from child
- Try shorthand syntax without <template> tags
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 🚀
