Inject HTML and components into your child components.
1What is a Slot?
A slot is an outlet in a component's template. It allows the parent component to pass template content (HTML, other components) directly into the child component's layout. Think of it like passing a function as a callback, but for HTML.
2Fallback Content
You can specify fallback content by placing elements inside the <slot></slot> tags in the child. If the parent provides content, the fallback is replaced. If not, the fallback is rendered.
3Named Slots
Sometimes you need multiple slots in a single component (e.g., a header slot, a body slot, a footer slot). You can name slots using <slot name="header">. The parent targets these using <template #header>.
4Step-by-Step Breakdown
Props are great for passing data (strings, numbers) to a child component. But what if you want to pass actual HTML or other components?
For passing HTML templates, Vue provides the <slot> element. It acts as a placeholder for content provided by the parent.
Which Vue element acts as an outlet or placeholder for template content passed down from a parent component?
- →yield
- →outlet
- →slot
When the parent uses the component, any HTML placed INSIDE the component's tags replaces the <slot> element.
You can provide fallback (default) content inside the <slot> tags. If the parent doesn't provide any content, the fallback is shown.
If a parent component uses <Card></Card> (without putting anything inside the tags), what happens to the fallback content inside <slot>Fallback</slot> in the Card component?
- →It causes an error
- →It is hidden
- →It is displayed
What if you need multiple slots? Like a Header, Body, and Footer in a Modal? You use Named Slots.
To target a named slot from the parent, you use the <template v-slot:name> syntax, which has a shorthand: #name.
What is the shorthand symbol for v-slot: when targeting a named slot?
- →@
- →#
- →:
Slots allow you to create highly flexible "Wrapper" components like Cards, Modals, and Layouts while keeping styling centralized.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Slots Preserve the Parent's Semantic Intent
Because slot content is authored by the parent and simply projected into the child's template, a parent can place a proper <h2> or an aria-labelledby-linked heading inside a <Modal> component's header slot, rather than the Modal component forcing a generic, non-descriptive heading level on every usage.
<Modal>
<template #header><h2 id="del-title">Delete item?</h2></template>
</Modal>SEO Implications
- 1
Slot Content Renders as Regular DOM, Not Hidden Markup
Content passed into a slot is not fetched lazily or rendered into a detached fragment — it becomes part of the same render tree as the rest of the component, so text placed inside a <Card> component's default slot is exactly as indexable by a crawler as text written directly in the parent's own template.
Best Practices
Always Provide Sensible Fallback Content for Optional Slots
Wrap default content inside the <slot></slot> tags (<slot>No items yet.</slot>) for slots that are genuinely optional, so a parent that forgets to pass anything gets a meaningful empty state instead of a blank gap in the layout.
Frequent Bugs
Trying to access a child component's local data from a regular (non-scoped) slot.
A default slot only receives whatever HTML the parent wrote — it has no access to the child's internal ref or reactive state. To expose child-owned data to slot content, the child must pass it via a scoped slot: <slot :item="internalItem" />, and the parent reads it with <template #default="{ item }">.
Real-World Examples
A Generic Data Table with a Scoped Slot
A reusable <DataTable :rows="users"> component loops over rows internally but exposes each row back to the parent via a scoped slot, letting every page decide exactly how to render its own columns without the DataTable component knowing anything about user-specific fields.
<!-- DataTable.vue -->
<tr v-for="row in rows"><slot :row="row" /></tr>
<!-- Parent -->
<DataTable :rows="users">
<template #default="{ row }">
<td>{{ row.email }}</td>
</template>
</DataTable>