Explain how Vue slots work (default, named, and scoped slots), what problem they solve, and how slot props allow a child component to pass data back to the parent’s template while still keeping one-way data flow.
Explain Slots in Vue: default vs named vs scoped slots — and how slot props enable child-to-parent data flow
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea
Slots are Vue’s way of letting a parent inject template content into a child — while the child controls where and how that content is rendered. With scoped slots (slot props), the child can also expose data to the parent’s slot template, enabling a powerful, explicit, and safe form of child → parent data flow.
Slot type | What it does | When you use it |
|---|---|---|
Default slot | Injects unnamed content into a child component | Simple wrapper components (Card, Modal, Layout, etc.) |
Named slot | Lets you target multiple insertion points | Complex layouts (header/body/footer, actions, sidebars) |
Scoped slot (slot props) | Child exposes data to the parent’s slot template | Reusable logic components (List, Table, Fetcher, Virtualizer) |
Default slot
The simplest case: the parent passes some markup, and the child decides where to render it.
<!-- Card.vue -->
<template>
<div class="card">
<slot />
</div>
</template>
<!-- Parent.vue -->
<Card>
<h2>Hello</h2>
<p>This goes into the default slot</p>
</Card>
Named slots
When a component has multiple insertion points, you name them.
<!-- Modal.vue -->
<template>
<div class="modal">
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
</div>
</template>
<!-- Parent.vue -->
<Modal>
<template #header>
<h2>Confirm</h2>
</template>
Are you sure?
<template #footer>
<button>OK</button>
</template>
</Modal>
The real power: scoped slots (slot props)
Normally, slot content is compiled in the parent’s scope. But sometimes the child has the data (e.g., a list, fetched results, virtualized rows). Scoped slots let the child expose data to the parent’s slot template explicitly.
<!-- ListRenderer.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="item.id" />
</li>
</ul>
</template>
<script setup>
const props = defineProps({
items: { type: Array, required: true }
});
</script>
Now the parent can decide how each item is rendered, using data provided by the child:
<!-- Parent.vue -->
<ListRenderer :items="users">
<template #default="{ item }">
<strong>{{ item.name }}</strong> ({{ item.email }})
</template>
</ListRenderer>
Why this is not “breaking” one-way data flow
The child is not mutating parent state. It is only exposing data. The parent still decides what to render and what to do with that data. This is inversion of control, not two-way binding.
Without scoped slots | With scoped slots |
|---|---|
Child controls both logic and rendering | Child controls logic, parent controls rendering |
Low flexibility | Highly reusable and customizable components |
Hard to generalize components | Enables headless / renderless components |
Common real-world use cases
- Table components (you provide column templates)
- List / virtual scroller components
- Data fetcher components
- Form builders
- Headless UI components
Interview-ready takeaway
Default slots inject content, named slots target multiple regions, and scoped slots let the child pass data into the parent’s slot template. This enables highly reusable components where the child owns the logic, the parent owns the rendering — without breaking one-way data flow.