Wat is een slot?

Stel je maakt een Card component met een mooie rand en schaduw. De inhoud verschilt elke keer — soms een titel met tekst, soms een afbeelding, soms een knop. Wat doe je?

Met slots laat het Card-component een gat waar de parent inhoud in kan stoppen.

Vergelijking met props:

  • Props → data doorgeven (strings, numbers, arrays)
  • Slots → HTML / componenten doorgeven

Vue vs React: Dit is Vue's tegenhanger van React's children prop, alleen krachtiger — je kunt meerdere gaten maken op verschillende plekken (named slots).

Default slot — de basis

Het child component

<!-- src/components/Card.vue -->
<template>
  <div class="card">
    <slot />
  </div>
</template>

<style scoped>
.card {
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>

Het parent component

<script setup>
import Card from './components/Card.vue'
</script>

<template>
  <Card>
    <h2>Hallo!</h2>
    <p>Dit komt in het slot terecht.</p>
  </Card>

  <Card>
    <img src="/photo.jpg" />
  </Card>
</template>

Wat gebeurt er? Alles wat je tussen <Card>...</Card> zet, vervangt de <slot /> in het Card component. De rand, padding en schaduw zijn telkens hetzelfde — de inhoud verschilt.

Fallback content (standaard inhoud)

Wil je iets tonen als de parent niets meegeeft? Zet de fallback binnen de slot-tag.

<!-- Button.vue -->
<template>
  <button>
    <slot>Klik hier</slot>
  </button>
</template>
<!-- Gebruik -->
<Button />                  <!-- Toont: Klik hier -->
<Button>Versturen</Button>  <!-- Toont: Versturen -->

Named slots — meerdere gaten

Wil je een component met meerdere slots (bijv. header + body + footer)? Geef ze een naam.

Het child component

<!-- src/components/Card.vue -->
<template>
  <div class="card">
    <header>
      <slot name="header" />
    </header>

    <main>
      <slot />   <!-- default slot -->
    </main>

    <footer>
      <slot name="footer" />
    </footer>
  </div>
</template>

Het parent component

Gebruik <template #naam> om aan te geven welke slot je vult:

<Card>
  <template #header>
    <h2>Mijn Titel</h2>
  </template>

  <!-- Zonder # wrapper gaat het naar de default slot -->
  <p>Dit is de hoofdtekst.</p>

  <template #footer>
    <button>Sluiten</button>
  </template>
</Card>

De # is een afkorting voor v-slot:. Beide werken:

<template #header>...</template>        <!-- kort -->
<template v-slot:header>...</template>  <!-- lang -->

Compleet voorbeeld — Modal

Een modal-component is de perfecte plek voor named slots: header, content en footer-knoppen zijn altijd anders, maar de overlay en sluit-knop blijven hetzelfde.

Modal.vue

<!-- src/components/Modal.vue -->
<script setup>
defineProps({
  isOpen: Boolean
})
defineEmits(['close'])
</script>

<template>
  <div v-if="isOpen" class="overlay" @click="$emit('close')">
    <div class="modal" @click.stop>

      <header class="modal-header">
        <slot name="title">Standaard titel</slot>
        <button @click="$emit('close')">×</button>
      </header>

      <main class="modal-body">
        <slot />
      </main>

      <footer class="modal-footer">
        <slot name="actions">
          <button @click="$emit('close')">OK</button>
        </slot>
      </footer>

    </div>
  </div>
</template>

Gebruik

<script setup>
import { ref } from 'vue'
import Modal from './components/Modal.vue'

const showModal = ref(false)
</script>

<template>
  <button @click="showModal = true">Open modal</button>

  <Modal :isOpen="showModal" @close="showModal = false">
    <template #title>
      <h2>Account verwijderen?</h2>
    </template>

    <p>Dit kun je niet ongedaan maken.</p>

    <template #actions>
      <button @click="showModal = false">Annuleer</button>
      <button @click="deleteAccount">Verwijder</button>
    </template>
  </Modal>
</template>

Voordeel: Het Modal-component beheert de overlay, focus en sluit-logica één keer. Voor elk soort modal — bevestiging, formulier, foto-viewer — gebruik je dezelfde Modal met andere slot-inhoud.

Belangrijke Regels

  • Default slot<slot /> in child, content tussen tags in parent
  • Named slot<slot name="x" /> in child, <template #x>...</template> in parent
  • Fallback content → tussen de slot-tags: <slot>default</slot>
  • Slots gaan over HTML/componenten, props over data

Veelgemaakte Fouten

Fout — slot vergeten in child:

<!-- Card.vue -->
<div class="card">
  <!-- ❌ geen slot, parent content verdwijnt -->
</div>

Goed:

<div class="card">
  <slot />   <!-- ✅ -->
</div>

Fout — name="..." bij parent in plaats van #:

<Card>
  <template name="header">...</template>     <!-- ❌ werkt niet -->
</Card>

Goed:

<Card>
  <template #header>...</template>          <!-- ✅ -->
</Card>