Vue.js Slots
Slots let a parent component inject HTML content into a child component. Props pass data values. Slots pass entire chunks of template content. This makes components far more flexible — the child defines the structure and the parent fills in the content.
The Problem Slots Solve
Imagine you build a Card component with a styled box. Without slots, the card always shows the same fixed content. With slots, the parent decides what goes inside the card — a product description, a user profile, a form — while the card handles the box styling.
Diagram: Slot as a Content Hole
Card Component (child): ┌────────────────────────────────┐ │ ┌──────────────────────────┐ │ │ │ <slot></slot> │ │ ← hole for parent to fill │ └──────────────────────────┘ │ └────────────────────────────────┘ Parent fills the slot differently each time: ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Product info │ │ User profile │ │ Login form │ └──────────────┘ └──────────────┘ └──────────────┘ Same Card component, different content each time.
Default Slot — Basic Usage
Add <slot></slot> inside the child component's template. Whatever the parent places between the component's opening and closing tags appears in that slot.
<!-- Child: BaseCard.vue -->
<template>
<div class="card">
<slot></slot>
</div>
</template>
<!-- Parent using BaseCard --> <BaseCard> <h3>Welcome Back!</h3> <p>Your last login was yesterday.</p> </BaseCard> <BaseCard> <img src="avatar.jpg" alt="User photo"> <p>Profile updated successfully.</p> </BaseCard>
Rendered Output
┌─────────────────────────────────┐ │ Welcome Back! │ │ Your last login was yesterday. │ └─────────────────────────────────┘ ┌─────────────────────────────────┐ │ [avatar image] │ │ Profile updated successfully. │ └─────────────────────────────────┘
Slot Default Content (Fallback)
You can place content between <slot> tags as a fallback. This fallback shows only if the parent provides nothing for that slot.
<!-- Child -->
<template>
<div class="card">
<slot>
<p>No content provided.</p>
</slot>
</div>
</template>
<!-- Parent passes content: fallback not shown --> <BaseCard><p>Hello!</p></BaseCard> <!-- Parent passes nothing: fallback shows --> <BaseCard></BaseCard>
Diagram: Fallback Slot Logic
Parent provides content? Yes → display parent's content No → display fallback content from <slot> tag
Named Slots
A component can have multiple slots by giving each one a name. The parent uses v-slot:name (or the #name shorthand) to target each named slot.
<!-- Child: PageLayout.vue -->
<template>
<div class="layout">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- default slot -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<!-- Parent filling named slots -->
<PageLayout>
<template #header>
<h1>My Blog</h1>
<nav>Home | About | Contact</nav>
</template>
<!-- Default slot (no name needed) -->
<article>
<h2>Post Title</h2>
<p>Article content goes here.</p>
</article>
<template #footer>
<p>© 2025 My Blog. All rights reserved.</p>
</template>
</PageLayout>
Diagram: Named Slots Fill Specific Positions
PageLayout renders as: ┌──────────────────────────────────────────┐ │ HEADER │ │ My Blog │ │ Home | About | Contact │ ├──────────────────────────────────────────┤ │ MAIN (default slot) │ │ Post Title │ │ Article content goes here. │ ├──────────────────────────────────────────┤ │ FOOTER │ │ © 2025 My Blog. All rights reserved. │ └──────────────────────────────────────────┘
Named Slot Shorthand
v-slot:header can be shortened to #header. Both work identically.
<!-- Full syntax --> <template v-slot:header>...</template> <!-- Shorthand --> <template #header>...</template>
Scoped Slots — Child Data Exposed to Parent
A scoped slot lets the child pass data back to the parent's slot content. The child exposes data on the <slot> element as attributes. The parent receives that data and uses it to render the content.
Think of it like a restaurant menu printed by the restaurant (child) but read and ordered from by the customer (parent). The parent decides what to display, but the child supplies the data.
<!-- Child: DataList.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="index"></slot>
</li>
</ul>
</template>
<script>
export default {
props: ["items"]
};
</script>
<!-- Parent decides how each item is displayed -->
<DataList :items="products" v-slot="{ item }">
<strong>{{ item.name }}</strong> — ${{ item.price }}
</DataList>
Diagram: Scoped Slot Data Flow
Child iterates items:
item = { id: 1, name: "Keyboard", price: 45 }
│
│ exposed via :item="item"
▼
Parent's slot receives it:
v-slot="{ item }"
│
▼
Parent's template uses it:
{{ item.name }} — ${{ item.price }}
→ "Keyboard — $45"
Parent controls how items look.
Child controls which items exist.
Practical Example: Reusable Modal
<!-- Child: BaseModal.vue -->
<template>
<div class="modal-overlay" v-if="show">
<div class="modal">
<div class="modal-header">
<slot name="title">Dialog</slot>
<button @click="$emit('close')">✕</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="actions">
<button @click="$emit('close')">Close</button>
</slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: ["show"],
emits: ["close"]
};
</script>
<!-- Parent uses the modal for a delete confirmation -->
<BaseModal :show="showDeleteModal" @close="showDeleteModal = false">
<template #title>Confirm Delete</template>
<p>Are you sure you want to delete this item?</p>
<p>This action cannot be undone.</p>
<template #actions>
<button @click="deleteItem">Yes, Delete</button>
<button @click="showDeleteModal = false">Cancel</button>
</template>
</BaseModal>
Diagram: Modal with Three Slots
┌──────────────────────────────────┐ │ #title slot: "Confirm Delete" ✕ │ ├──────────────────────────────────┤ │ default slot: │ │ "Are you sure..." │ │ "This action cannot..." │ ├──────────────────────────────────┤ │ #actions slot: │ │ [Yes, Delete] [Cancel] │ └──────────────────────────────────┘ The modal handles: overlay, layout, close button The parent handles: title text, body content, action buttons
Summary
Slots let parent components inject content into child components. The default slot handles single content areas. Named slots (#name) let the parent target multiple specific regions inside the child. Scoped slots allow the child to pass its own data back up to the parent's slot content, giving the parent control over rendering while the child controls the data. Slots are the key to building truly reusable layout components like cards, modals, panels, and tables.
