patternjavascriptvueTip
Vue 3 named and scoped slots for flexible component composition
Viewed 0 times
Vue 3.0+
slotsnamed slotsscoped slotsv-slotslot contentrenderless components
Problem
Components with hard-coded content can't be reused across different contexts. Named slots and scoped slots are underused, leading to prop-heavy APIs instead of flexible composition.
Solution
Use named slots to split content areas; scoped slots to expose data to the parent:
<!-- Card.vue -->
<template>
<div class="card">
<header>
<slot name="header">Default Header</slot>
</header>
<main>
<slot /> <!-- default slot -->
</main>
<footer>
<!-- Scoped slot: exposes internal data to parent -->
<slot name="footer" :actions="{ save, discard }" />
</footer>
</div>
</template>
<script setup>
function save() { / ... / }
function discard() { / ... / }
</script>
<!-- Parent usage -->
<Card>
<template #header>
<h1>My Title</h1>
</template>
<p>Main body content here.</p>
<template #footer="{ actions }">
<button @click="actions.save">Save</button>
<button @click="actions.discard">Discard</button>
</template>
</Card>
<!-- Card.vue -->
<template>
<div class="card">
<header>
<slot name="header">Default Header</slot>
</header>
<main>
<slot /> <!-- default slot -->
</main>
<footer>
<!-- Scoped slot: exposes internal data to parent -->
<slot name="footer" :actions="{ save, discard }" />
</footer>
</div>
</template>
<script setup>
function save() { / ... / }
function discard() { / ... / }
</script>
<!-- Parent usage -->
<Card>
<template #header>
<h1>My Title</h1>
</template>
<p>Main body content here.</p>
<template #footer="{ actions }">
<button @click="actions.save">Save</button>
<button @click="actions.discard">Discard</button>
</template>
</Card>
Why
Named slots divide a component into multiple injection points, letting the parent compose each section independently. Scoped slots invert control: the child exposes its data and the parent decides how to render it — useful for data-table rows, list item templates, and renderless components.
Gotchas
- v-slot:#name shorthand requires a template tag for named slots
- Default slot content is rendered if the parent provides nothing — always add sensible defaults
- Scoped slot data is only available within the template tag where #slotName="scope" is declared
- $slots.header can be checked to conditionally render the slot container
Code Snippets
Only render header wrapper if slot has content
<!-- Conditional slot container -->
<header v-if="$slots.header">
<slot name="header" />
</header>Context
When building reusable Vue 3 components with flexible content areas
Revisions (0)
No revisions yet.