Slots und Scoped Slots für flexible UI-Komponenten in Vue.js
AI generated
<v/>
{ }
Vue.js · Slots · Scoped Slots · UI-Komponenten
Slots und Scoped Slots in Vue.js
Flexible UI-Komponenten durch maximale Konsumentenkontrolle

Props machen Komponenten konfigurierbar. Slots machen sie erweiterbar. Scoped Slots geben dem Konsumenten die Kontrolle über die Darstellung — während die Komponente die Logik behält. Wer Slots und Scoped Slots in Vue.js wirklich versteht, baut Komponenten, die sich in jedem Kontext nahtlos einfügen.

13 Min. Lesezeit Default · Named · Scoped Slots · v-slot · Slot-Props Vue 3 · Composition API · TypeScript

1. Slots in Vue.js: Mehr als Inhalts-Platzhalter

Slots in Vue.js sind das primäre Werkzeug, um Komponenten von innen erweiterbar zu machen. Der Unterschied zu Props ist fundamental: Props konfigurieren das Verhalten einer Komponente mit primitiven Werten oder Objekten. Slots geben dem Konsumenten die Kontrolle über den Inhalt — vollständiges HTML, andere Komponenten, reaktive Ausdrücke. Das macht Slots zum richtigen Werkzeug für alle UI-Komponenten, die in ihrer Struktur flexibel bleiben sollen, ohne für jeden Anwendungsfall mit Props überladen zu werden.

Das Slot-System von Vue ist in drei Stufen aufgebaut: Default Slots für einfache Inhaltserweiterung, Named Slots für mehrere klar benannte Inhaltsbereiche, und Scoped Slots für die umgekehrte Datenflusskette — die Komponente gibt Daten in den Slot, der Konsument entscheidet, wie diese Daten dargestellt werden. Alle drei Mechanismen arbeiten im selben Template-Syntax-Rahmen und lassen sich kombinieren. Wer alle drei beherrscht, kann Komponenten bauen, die in keinem Kontext die Kontrolle über das Markup wegnehmen.

2. Default Slot: Einfache Erweiterbarkeit ohne Namenskonvention

Der Default Slot in Vue ist der einfachste Einstieg in das Slot-System. Eine Komponente platziert ein <slot />-Element im Template — alles, was der Konsument zwischen den öffnenden und schließenden Tags der Komponente schreibt, wird an dieser Stelle gerendert. Kein Prop-Gymnastics, kein v-html, kein dynamischer Komponentenname. Der Default Slot ist das fundamentale Kompositionsmuster für Wrapper-Komponenten: Cards, Panels, Modals, Alerts — alle profitieren davon, ihren Inhaltsbereich via Default Slot zugänglich zu machen.

Ein häufiger Fehler beim Default Slot: die Komponente enthält zwar ein <slot />, hat aber gleichzeitig so viele opinionierte Styles auf dem Wrapper-Element, dass der Slot-Inhalt visuell nicht mehr flexibel ist. Die richtige Balance liegt darin, strukturelle Styles — Abstände, Rahmen, Hintergrund — auf der Komponente zu definieren und dem Slot nur den Inhaltsbereich zu überlassen. Wenn der Konsument die Struktur ändern muss, braucht er mehr als einen Default Slot — dann kommen Named Slots ins Spiel.


<!-- BaseCard.vue — Structural wrapper with default slot -->
<template>
  <div class="rounded-2xl border border-slate-200 bg-white shadow-sm overflow-hidden">
    <!-- Optional header named slot -->
    <div v-if="$slots.header" class="px-6 py-4 border-b border-slate-100 bg-slate-50">
      <slot name="header" />
    </div>

    <!-- Default slot: consumer controls all content -->
    <div class="px-6 py-5">
      <slot />
    </div>

    <!-- Optional footer named slot -->
    <div v-if="$slots.footer" class="px-6 py-4 border-t border-slate-100 bg-slate-50">
      <slot name="footer" />
    </div>
  </div>
</template>

<!-- Consumer usage — full control over slot content -->
<!-- <BaseCard>
  <template #header>
    <h2 class="font-bold text-slate-900">Bestellübersicht</h2>
  </template>

  <p class="text-slate-600">Ihre Bestellung wird verarbeitet...</p>

  <template #footer>
    <button class="bg-green-600 text-white px-4 py-2 rounded-lg">Bestätigen</button>
  </template>
</BaseCard> -->

3. Named Slots: Mehrere Inhaltsbereiche strukturieren

Named Slots in Vue ermöglichen es, mehrere klar definierte Inhaltsbereiche in einer Komponente anzubieten. Das klassische Beispiel ist ein Layout-Slot-System: header, sidebar, main, footer. Jeder Bereich hat seine eigene strukturelle Rolle in der Komponente, der Konsument füllt sie mit beliebigem Inhalt. Named Slots sind die richtige Wahl immer dann, wenn eine Komponente eine komplexere Layout-Struktur vorgibt, die Inhalte aber vollständig austauschbar sein sollen.

Die Syntax in Vue 3 ist v-slot:name oder die Kurzform #name. Ein häufiger Fehler: Named Slots ohne v-if="$slots.name"-Guard zu rendern. Wenn der Konsument keinen Inhalt für einen Named Slot angibt, rendert die Komponente trotzdem den Wrapper-Container — mit leerem Inhalt und möglicherweise unerwünschten Padding- oder Border-Stilen. Der korrekte Umgang ist die Prüfung auf $slots.name im Template: Der Wrapper-Container wird nur gerendert, wenn der entsprechende Slot tatsächlich Inhalt hat.

4. Scoped Slots: Daten vom Kind an den Elternteil zurück

Scoped Slots in Vue kehren die Datenflusskette um: Normalerweise fließen Daten von der Elternkomponente via Props zur Kindkomponente. Mit Scoped Slots gibt die Kindkomponente Daten in den Slot, den der Elternteil rendert. Das ermöglicht ein mächtiges Muster: Die Kindkomponente verwaltet State und Logik, der Konsument entscheidet, wie diese Daten dargestellt werden. Eine virtuelle Liste rendert zum Beispiel nur sichtbare Elemente — aber wie jedes Element aussieht, entscheidet der Konsument per Scoped Slot.

In Vue 3 sind Scoped Slots syntaktisch einheitlich mit Normal-Slots: v-slot:name="slotProps" oder #name="slotProps" auf dem <template>-Tag. Die Slot-Props werden via Destructuring direkt in Template-Variablen aufgenommen: #item="{ data, index, isActive }". Das macht die Konsumenten-Seite sehr lesbar. Auf der Kindkomponenten-Seite werden Slot-Props als Attribute auf dem <slot>-Element gebunden: <slot name="item" :data="item" :index="i" :isActive="activeIndex === i" />.


<!-- DataTable.vue — Generic table with scoped slots for full cell control -->
<template>
  <div class="overflow-hidden rounded-2xl border border-slate-200">
    <table class="w-full text-sm">
      <thead class="bg-slate-900 text-white">
        <tr>
          <th
            v-for="col in columns"
            :key="col.key"
            class="text-left px-4 py-3 font-semibold"
          >
            <!-- Named scoped slot per column header — consumer can customize -->
            <slot :name="`header-${col.key}`" :column="col">
              { { col.label } }
            </slot>
          </th>
        </tr>
      </thead>
      <tbody class="divide-y divide-slate-200">
        <tr v-for="(row, rowIndex) in rows" :key="rowIndex" class="hover:bg-slate-50">
          <td v-for="col in columns" :key="col.key" class="px-4 py-3">
            <!-- Scoped slot per cell: consumer gets row, col, value, index -->
            <slot
              :name="`cell-${col.key}`"
              :row="row"
              :column="col"
              :value="row[col.key]"
              :rowIndex="rowIndex"
            >
              { { row[col.key] } }
            </slot>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup>
// Generic table component — accepts any columns/rows structure
defineProps({
  columns: { type: Array, required: true },
  rows: { type: Array, required: true },
})
</script>

5. Slot-Props gezielt gestalten und typisieren

Gut gestaltete Slot-Props in Vue sind das Gegenstück zu einer guten Props-API: Sie exponieren das, was der Konsument braucht, ohne interne Implementierungsdetails zu leaken. Das bedeutet: Keine raw Vue-Instanzen, keine internen Refs, kein ungetyptes Objekt als Catch-all. Stattdessen klar benannte, semantisch sinnvolle Slot-Props, die der Konsument direkt verwenden kann, ohne über die interne Datenstruktur nachzudenken.

In TypeScript-Projekten werden Slot-Props mit defineSlots() in Vue 3.3+ typisiert. Das Makro gibt dem TypeScript-Compiler die Information, welche Slots die Komponente anbietet und welche Props jeder Slot hat. Der Konsument bekommt vollständige Autovervollständigung beim Schreiben von #slotname="{ ... }". Das ist besonders wichtig für Komponentenbibliotheken, wo Konsumenten keinen Blick in den Quellcode werfen sollten, um die Slot-API zu verstehen.

6. Dynamische Slot-Namen für erweiterbare Tabellen und Listen

Dynamische Slot-Namen in Vue sind ein fortgeschrittenes, aber sehr praktisches Feature für generische UI-Komponenten. Das in Abschnitt 4 gezeigte DataTable-Beispiel nutzt genau dieses Muster: Statt für jede mögliche Spalte einen eigenen Named Slot hart zu codieren, werden die Slot-Namen dynamisch aus dem Column-Key abgeleitet — cell-name, cell-status, cell-price. Der Konsument kann genau die Spalten überschreiben, die er individuell gestalten möchte, während alle anderen mit dem Standard-Rendering der Komponente gerendert werden.

Die Syntax für dynamische Slot-Namen auf der Konsumentenseite ist #[dynamicSlotName]="slotProps". Das ist identisch mit dynamischen Props-Namen und funktioniert mit beliebigen JavaScript-Ausdrücken in eckigen Klammern. Dieses Muster macht Komponenten zu echten Erweiterungspunkten: Eine generische List-Komponente kann jedes Element individuell stylen lassen, ohne die Logik der Virtualisierung, des Lazyloading oder der Sortierung zu duplizieren.

7. Fallback-Inhalt und bedingte Slot-Rendering-Logik

Fallback-Inhalt in Vue-Slots ist der Inhalt, der gerendert wird, wenn der Konsument keinen Slot-Inhalt angibt. Er steht zwischen den öffnenden und schließenden <slot>-Tags in der Kind-Komponente. Das ermöglicht sinnvolle Standarddarstellungen: Ein Tooltip-Slot zeigt standardmäßig einen Info-Icon, kann aber durch beliebigen Konsumenten-Inhalt ersetzt werden. Der Fallback-Inhalt hat vollen Zugriff auf den Scope der Kind-Komponente — er ist Teil des Kind-Templates, nicht des Eltern-Templates.

Die programmatische Prüfung, ob ein Slot Inhalt hat, erfolgt via $slots.slotname im Template oder useSlots().slotname in der Composition API. Diese Prüfung ist wichtig, um Wrapper-Container für leere Slots zu vermeiden. Eine Card-Komponente sollte ihren Footer-Container nicht rendern, wenn kein Footer-Slot-Inhalt vorhanden ist — sonst entstehen leere Bereiche mit unnötigem Padding. v-if="$slots.footer" auf dem Wrapper-Container ist das korrekte Pattern für konditionales Slot-Rendering.

8. useSlots() und $slots für programmatischen Slot-Zugriff

In der Vue 3 Composition API ersetzt useSlots() die $slots-Eigenschaft der Options API. Das Composable gibt ein reaktives Objekt zurück, das alle Slot-Funktionen als Eigenschaften enthält. Der programmatische Zugriff auf Slots ermöglicht es, in der setup-Funktion auf das Vorhandensein von Slots zu reagieren — zum Beispiel zusätzliche CSS-Klassen zu setzen, wenn ein bestimmter Slot vorhanden ist, oder Logik zu aktivieren, die nur Sinn macht, wenn der Konsument einen bestimmten Slot befüllt.

Ein fortgeschrittenes Pattern: Die Komponente überprüft in setup, welche Slots der Konsument befüllt hat, und passt ihr Layout dynamisch an. Eine Sidebar-Layout-Komponente könnte den Hauptbereich auf volle Breite dehnen, wenn kein Sidebar-Slot vorhanden ist. Kombiniert mit computed Properties, die aus der Slot-Präsenz dynamische Klassen ableiten, entstehen Komponenten, die ihren eigenen Layout-Kontext erkennen und entsprechend reagieren — vollständig transparent für den Konsumenten.


// SidebarLayout.vue — Dynamic layout based on slot presence
<template>
  <div class="flex gap-6">
    <!-- Main content: full width if no sidebar slot provided -->
    <main :class="hasSidebar ? 'flex-1 min-w-0' : 'w-full'">
      <slot />
    </main>

    <!-- Sidebar: only rendered when slot has content -->
    <aside v-if="hasSidebar" class="w-64 flex-shrink-0">
      <slot name="sidebar" />
    </aside>
  </div>
</template>

<script setup>
import { useSlots, computed } from 'vue'

const slots = useSlots()

// Reactively check if sidebar slot is provided
const hasSidebar = computed(() => !!slots.sidebar)
</script>

// DataList.vue — Scoped slot with fallback and dynamic slot names
// <template>
//   <ul>
//     <li v-for="(item, i) in items" :key="item.id">
//       <!-- Named dynamic scoped slot: consumer can override per item type -->
//       <slot :name="`item-${item.type}`" :item="item" :index="i">
//         <!-- Fallback: generic item rendering when no custom slot given -->
//         <slot name="item" :item="item" :index="i">
//           <span>{ { item.label } }</span>
//         </slot>
//       </slot>
//     </li>
//   </ul>
// </template>

9. Slots vs. Props vs. Scoped Slots im Vergleich

Merkmal Props Default/Named Slots Scoped Slots
Datenfluss Eltern → Kind Eltern rendert, Kind platziert Kind gibt Daten an Eltern zurück
Markup-Kontrolle Keine Vollständig Vollständig mit Kind-Daten
Typische Anwendung Konfiguration, Verhalten Layout, Wrapper, Bereiche Listen, Tabellen, Renderless
TypeScript-Support Sehr gut Gut Gut mit defineSlots()
Fallback-Inhalt Defaultwert Slot-Fallback-Inhalt Slot-Fallback mit Kind-Daten
Komplexität Niedrig Mittel Höher

Die Entscheidung zwischen Props, Slots und Scoped Slots folgt einem klaren Muster: Props für Werte und Verhalten, Default Slots für Inhalte, Named Slots für mehrere strukturierte Inhaltsbereiche, Scoped Slots wenn die Kindkomponente Daten für die Darstellung bereitstellen muss. Wer Props verwendet, um HTML-Strings zu übergeben — :label="'<strong>Wichtig</strong>'" — hat das falsche Werkzeug gewählt. Das ist der sicherste Hinweis darauf, dass ein Slot gebraucht wird.

Mironsoft

Vue.js UI-Komponentenentwicklung, Design Systems und Komponentenbibliotheken

Flexible Vue-Komponenten, die in jedem Kontext funktionieren?

Wir entwickeln erweiterbare Vue-Komponenten mit sauberem Slot-Design — Default, Named und Scoped Slots, typisiert mit defineSlots() und nahtlos in euer Design System integriert.

Komponenten-Review

Analyse bestehender Komponenten auf falsch eingesetzte Props und fehlende Slot-Extensionpoints

Slot-API-Design

Klare, typsichere Slot-APIs mit defineSlots(), Fallback-Inhalten und dynamischen Slot-Namen

Generische Komponenten

DataTable, List und Layout-Komponenten mit Scoped Slots und dynamischen Slot-Namen

10. Zusammenfassung

Slots und Scoped Slots in Vue.js sind das zentrale Werkzeug für erweiterbare, flexible UI-Komponenten. Default Slots ermöglichen einfache Inhaltserweiterung ohne Namenskonvention. Named Slots strukturieren mehrere klar definierte Inhaltsbereiche. Scoped Slots kehren den Datenfluss um und ermöglichen der Kindkomponente, State an den Konsumenten zurückzugeben. Dynamische Slot-Namen machen generische Komponenten wie Tabellen und Listen in jedem Spalten- oder Element-Layout individuell anpassbar.

Die wichtigste Designregel für Slot-APIs: Slot-Props kleinstmöglich halten und nur das exponieren, was der Konsument wirklich braucht. v-if="$slots.name"-Guards verhindern leere Wrapper-Container. defineSlots() in TypeScript-Projekten ist kein Luxus, sondern eine DX-Investition für alle Konsumenten der Bibliothek. Wer Props für HTML verwendet, sollte zu Slots wechseln — das ist die zuverlässigste Regel im Vue-Slot-Design.

Slots in Vue.js — Das Wichtigste auf einen Blick

Default Slot

Einfachste Erweiterbarkeit. Konsument kontrolliert Inhalt vollständig. Fallback-Inhalt zwischen <slot>-Tags.

Named Slots

Mehrere Inhaltsbereiche strukturieren. v-if="$slots.name"-Guard für bedingte Container. Syntax: #name.

Scoped Slots

Kind gibt Daten an Elternteil. Perfekt für Listen, Tabellen, Renderless Components. Mit defineSlots() typisieren.

useSlots()

Programmatischer Slot-Zugriff in setup(). Reaktives Objekt aller Slot-Funktionen. Für dynamisches Layout basierend auf Slot-Präsenz.

11. FAQ: Slots und Scoped Slots in Vue.js

1Props vs. Slots in Vue?
Props für Werte und Verhalten. Slots für Markup-Kontrolle — HTML, Komponenten, reaktive Ausdrücke. HTML in Props ist das Zeichen, dass ein Slot gebraucht wird.
2Was sind Scoped Slots?
Umgekehrter Datenfluss: Kind stellt Daten via Slot-Props bereit. Konsument entscheidet über Darstellung. Syntax: #name="{ prop1, prop2 }".
3Slot auf Inhalt prüfen?
v-if="$slots.name" im Template. useSlots().name in setup(). Verhindert leere Wrapper-Container für nicht befüllte Slots.
4Dynamische Slot-Namen?
#[dynamicName]="slotProps" — für generische Tabellen und Listen, bei denen Konsumenten einzelne Spalten überschreiben.
5Slots in TypeScript typisieren?
defineSlots() in der Komponente. Konsumenten bekommen volle Autovervollständigung. Kein optionaler Schritt in Bibliotheken.
6Was ist Fallback-Inhalt?
Inhalt zwischen <slot>-Tags. Wird gerendert wenn Konsument nichts angibt. Hat Zugriff auf Kind-Scope, nicht auf Eltern-Scope.
7Named Slot statt Props?
Immer wenn HTML oder Komponenten übergeben werden sollen. HTML-String in Props ist ein Anti-Pattern — Named Slot ist die richtige Alternative.
8useSlots() in Composition API?
const slots = useSlots() — reaktives Objekt aller Slot-Funktionen. Für dynamisches Layout basierend auf Slot-Präsenz in setup().
9Slot-Forwarding zwischen Komponenten?
Ja — Slot-Inhalt via v-slot empfangen und als Slot an die nächste Kind-Komponente weitergeben. Slot-Forwarding-Pattern für Wrapper-Komponenten.
10Wie viele Named Slots sind sinnvoll?
So wenige wie möglich. Default + header + footer für Cards reicht meistens. Zu viele Slots signalisieren, dass die Komponente zu viel verantwortung hat.