Vue Slots
Slots let a component define placeholders in its own template that the parent using it gets to fill with content, turning rigid components into reusable containers.
What Problem Slots Solve
Props are great for passing data into a component, but they're awkward for passing markup -- you'd end up serializing HTML into a string prop, which brings back all the v-html safety concerns. Slots solve this by letting a child component declare a slot outlet in its template, and letting the parent write ordinary template markup, including other components, directly between the child's opening and closing tags. The child doesn't need to know what's inside the slot; it just decides where it goes.
Default Slots
The simplest slot has no name. A component drops a bare slot tag wherever it wants injected content to appear, and any markup a parent places between that component's tags fills that spot. Anything the child template puts around the slot -- borders, padding, headings -- still applies, since only the slot's placeholder is replaced. A slot tag can also contain its own fallback markup, which Vue renders only when the parent passes nothing in.
A Reusable Card with a Default Slot
<!-- Card.vue -->
<template>
<div class='card'>
<slot>No content provided yet.</slot>
</div>
</template>
<!-- Parent.vue -->
<script setup>
import Card from './Card.vue'
</script>
<template>
<Card>
<h3>Weekly Report</h3>
<p>Sales were up 12% compared to last week.</p>
</Card>
</template>Named Slots
A component can expose more than one slot outlet by giving each a name attribute, such as slot name='header'. Parents target a named slot with a template wrapper using v-slot:header, or the shorthand #header. A single unnamed slot is really just shorthand for a slot named default, so #default and a bare v-slot both refer to it.
Filling Three Named Slots
<!-- Layout.vue -->
<template>
<div class='layout'>
<header>
<slot name='header'>Untitled Page</slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name='footer'></slot>
</footer>
</div>
</template>
<!-- Parent.vue -->
<script setup>
import Layout from './Layout.vue'
</script>
<template>
<Layout>
<template #header>
<h1>Dashboard</h1>
</template>
<p>Main dashboard content goes here.</p>
<template #footer>
<small>Copyright 2026 Example Co.</small>
</template>
</Layout>
</template>Scoped Slots -- Passing Data Back Up
Sometimes the child holds data that the parent needs in order to render the slot content -- a list component that owns the array of items but wants the parent to control how each item looks. Bind that data to the slot tag as if it were a component prop, and the parent reads it back with v-slot='...' (or #default='...') on the template wrapper, destructuring whatever properties the child exposed.
A Scoped Slot That Hands Data to the Parent
<!-- ItemList.vue -->
<script setup>
const props = defineProps(['items'])
</script>
<template>
<ul>
<li v-for='(item, index) in items' :key='item.id'>
<slot name='item' :item='item' :index='index'>
{{ item.name }}
</slot>
</li>
</ul>
</template>
<!-- Parent.vue -->
<script setup>
import ItemList from './ItemList.vue'
const items = [
{ id: 1, name: 'Milk' },
{ id: 2, name: 'Bread' }
]
</script>
<template>
<ItemList :items='items'>
<template #item='{ item, index }'>
<strong>{{ index + 1 }}.</strong> {{ item.name }}
</template>
</ItemList>
</template>Slot Types at a Glance
Exercise: Vue Slots
Who supplies the content that fills a component's default slot?