Slots allow parent components to pass template content into child components. They're essential for creating flexible, reusable components.

The Default Slot

The simplest slot usage:

vue
<!-- Card.vue -->
<template>
  <div class="card">
    <slot></slot>
  </div>
</template>
vue
<!-- Parent -->
<Card>
  <h2>Card Title</h2>
  <p>This content goes into the slot!</p>
</Card>

The content between <Card> tags replaces <slot>.

Fallback Content

Provide default content when the slot is empty:

vue
<!-- SubmitButton.vue -->
<template>
  <button type="submit" class="btn">
    <slot>Submit</slot>
  </button>
</template>
vue
<!-- Parent -->
<SubmitButton />               <!-- Shows: Submit -->
<SubmitButton>Save Changes</SubmitButton>  <!-- Shows: Save Changes -->
<SubmitButton>                 <!-- Shows: Submit (empty content) -->
</SubmitButton>

Named Slots

Create multiple slots with names:

vue
<!-- BaseLayout.vue -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    
    <main>
      <slot></slot>  <!-- Default slot -->
    </main>
    
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

Use v-slot directive (or # shorthand) to target named slots:

vue
<!-- Parent -->
<BaseLayout>
  <template #header>
    <h1>My Page Title</h1>
    <nav>Navigation here</nav>
  </template>
  
  <!-- Default slot content -->
  <p>Main content goes here...</p>
  <p>More content...</p>
  
  <template #footer>
    <p>Copyright 2024</p>
  </template>
</BaseLayout>

Slot Shorthand:

Use # instead of v-slot::

vue
<template>
  <!-- Full syntax -->
  <template v-slot:header>...</template>
  <template v-slot:footer>...</template>
  
  <!-- Shorthand -->
  <template #header>...</template>
  <template #footer>...</template>
</template>

Conditional Slots

Check if a slot has content with $slots:

vue
<!-- Card.vue -->
<script setup>
import { useSlots } from 'vue'

const slots = useSlots()
</script>

<template>
  <div class="card">
    <header v-if="slots.header" class="card-header">
      <slot name="header"></slot>
    </header>
    
    <div class="card-body">
      <slot></slot>
    </div>
    
    <footer v-if="slots.footer" class="card-footer">
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

Dynamic Slot Names

Use dynamic values for slot names:

vue
<script setup>
import { ref } from 'vue'

const currentSlot = ref('header')
</script>

<template>
  <BaseLayout>
    <template #[currentSlot]>
      <p>This content goes to: {{ currentSlot }}</p>
    </template>
  </BaseLayout>
  
  <button @click="currentSlot = 'footer'">Move to Footer</button>
</template>

Practical Example: Modal Component

vue
<!-- Modal.vue -->
<script setup lang="ts">
const props = defineProps<{
  isOpen: boolean
}>()

const emit = defineEmits<{
  close: []
}>()
</script>

<template>
  <Teleport to="body">
    <div v-if="isOpen" class="modal-overlay" @click.self="emit('close')">
      <div class="modal">
        <header v-if="$slots.header" class="modal-header">
          <slot name="header"></slot>
          <button class="close-btn" @click="emit('close')">×</button>
        </header>
        
        <div class="modal-body">
          <slot></slot>
        </div>
        
        <footer v-if="$slots.footer" class="modal-footer">
          <slot name="footer"></slot>
        </footer>
      </div>
    </div>
  </Teleport>
</template>
vue
<!-- Parent -->
<Modal :is-open="showModal" @close="showModal = false">
  <template #header>
    <h2>Confirm Action</h2>
  </template>
  
  <p>Are you sure you want to proceed?</p>
  
  <template #footer>
    <button @click="showModal = false">Cancel</button>
    <button @click="confirm">Confirm</button>
  </template>
</Modal>
✓ Completed