Vue Slots: Solving the Layout Position Problem
You’re building a notification system. Desktop users expect alerts in the top-right corner. Mobile users need them at the bottom for easy thumb access. Same alert component, completely different DOM locations. Not just CSS positioning—actually different places in your markup tree.
The Problem
Here’s the challenge: you need one alert component that renders in different structural positions depending on viewport size. You could duplicate the component code for desktop and mobile, but that’s a maintenance nightmare. CSS alone won’t work because you’re dealing with different parent containers and z-index contexts.
Desktop wants alerts here:
<div class="app">
<header>
<div class="top-right-alerts">
<!-- Alerts -->
</div>
</header>
<main>...</main>
</div>
Mobile needs them here:
<div class="app">
<main>...</main>
<footer>
<div class="bottom-alerts">
<!-- Alerts -->
</div>
</footer>
</div>
Vue slots solve this elegantly.
What Are Slots?
Slots let parent components inject content into child components. The child says “I have a placeholder here” and the parent decides what fills it. It’s Vue’s content distribution API, inspired by the Web Components specification.
In Vue, slots allow a component to accept dynamic content—called slot content—and render it in specific locations within the component’s template, marked by the <slot> element. This solves the problem of fixed templates by enabling customization and reusability.
For example, a <FancyButton> component might look like:
<button class="fancy-btn">
<slot></slot> <!-- slot outlet -->
</button>
When used like:
<FancyButton>Click me!</FancyButton>
The final rendered output is:
<button class="fancy-btn">Click me!</button>
Slots are extremely flexible—they can contain plain text, HTML elements, or even other components—making your Vue components more composable and adaptable.
Three Types of Slots
Default Slots are unnamed and handle simple content insertion. Perfect for buttons, cards, or any component with a single content area.
Named Slots let you have multiple placeholders in one component:
<!-- Modal.vue -->
<template>
<div class="modal">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<!-- Usage -->
<Modal>
<template #header>
<h2>Confirm Action</h2>
</template>
<p>Are you sure?</p>
<template #footer>
<button>Cancel</button>
<button>Confirm</button>
</template>
</Modal>
The #header syntax is shorthand for v-slot:header as explained in the Vue slots guide.
Scoped Slots let child components pass data back through the slot:
<!-- DataList.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item"></slot>
</li>
</ul>
</template>
<!-- Usage -->
<DataList :items="products">
<template #default="{ item }">
<strong>{{ item.name }}</strong> - ${{ item.price }}
</template>
</DataList>
The child exposes item through the slot, and the parent decides how to render it.
Solving the Alert Position Problem
Here’s how slots fix our notification positioning:
<!-- App.vue -->
<template>
<div class="app">
<AlertContainer>
<template #desktop>
<div class="top-right">
<Alert v-for="alert in alerts" :key="alert.id" />
</div>
</template>
<template #mobile>
<div class="bottom-bar">
<Alert v-for="alert in alerts" :key="alert.id" />
</div>
</template>
</AlertContainer>
</div>
</template>
<!-- AlertContainer.vue -->
<template>
<div>
<slot name="desktop" v-if="!isMobile"></slot>
<slot name="mobile" v-if="isMobile"></slot>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const isMobile = ref(window.innerWidth < 768)
onMounted(() => {
window.addEventListener('resize', () => {
isMobile.value = window.innerWidth < 768
})
})
</script>
Same component, different rendering locations, no duplication.
When to Use Which Type
Use default slots when:
- Your component has one content area
- You’re building simple wrappers like buttons or cards
- The content is straightforward HTML or text
Use named slots when:
- You need multiple distinct content areas
- Building layout components (headers, sidebars, footers)
- Creating modals or dialogs with separate sections
Use scoped slots when:
- Child component manages data but parent controls rendering
- Building data tables where rows need custom formatting
- Creating list components with customizable item display
Conditional Slots and Fallbacks
Check if a slot has content before rendering wrapper elements:
<template>
<div class="card">
<header v-if="$slots.header">
<slot name="header"></slot>
</header>
<slot></slot>
</div>
</template>
This prevents empty <header> tags when the header slot isn’t used.
Provide fallback content for optional slots:
<template>
<button>
<slot>Click Me</slot>
</button>
</template>
“Click Me” appears unless you override it with your own content.
Slots vs Props: The Decision
Use props for configuration and data:
<UserCard
:name="user.name"
:role="user.role"
:active="user.active"
/>
Use slots for markup and composition:
<UserCard>
<template #avatar>
<img :src="user.avatar" />
</template>
<template #bio>
<p>{{ user.description }}</p>
<a :href="user.website">Website</a>
</template>
</UserCard>
If you’re passing strings, numbers, or booleans—use props. If you’re passing HTML or components—use slots.
Common Pitfalls
Slot names are case-sensitive:
<!-- Won't work -->
<template #Header>...</template>
<slot name="header"></slot>
<!-- Will work -->
<template #header>...</template>
<slot name="header"></slot>
Vue 3 requires template syntax for named slots:
<!-- Old Vue 2 style - doesn't work in Vue 3 -->
<Modal>
<div slot="header">Title</div>
</Modal>
<!-- Vue 3 style -->
<Modal>
<template #header>
<div>Title</div>
</template>
</Modal>
The slot attribute was removed in Vue 3.
Conclusion
Slots are Vue’s answer to flexible component composition. They let you build containers that accept any content while keeping your code maintainable. The alert positioning problem we started with becomes trivial—just render different slots based on viewport size.
Key Takeaways:
Start with default slots for simple cases. Use named slots when you need multiple content areas. Reach for scoped slots when child components need to share data. Always provide fallback content for optional slots. Check $slots before rendering wrapper elements. Choose slots over props when passing markup or components.
Tips for Clean Slot Code:
Keep slot names descriptive. Use header, footer, sidebar instead of slot1, slot2. Don’t overdo it—too many slots make components confusing. If you have more than five named slots, reconsider your component structure. Document expected slot content with comments explaining what each slot is for. Use conditional rendering efficiently with v-if on slots to avoid mounting unnecessary components.
Further Reading:
Dive deeper into slots with the Vue.js official slots documentation. Learn about the underlying web standard in MDN’s Web Components slot element guide. Build on this foundation with Vue.js component basics.