Komponenten in Svelte komponieren sich, sie erben nicht. Eine Komponente lässt sich durch Props konfigurieren, durch children und benannte Snippets erweitern und durch Verschachtelung in größere Strukturen einbauen. Wer dieses Prinzip verinnerlicht, braucht keine Klassenvererbung — selbst komplexe UI-Bibliotheken kommen mit Komposition aus.

Warum keine Vererbung?

Klassische OOP würde eine Button-Klasse haben, davon PrimaryButton extends Button, vielleicht noch IconButton extends PrimaryButton. In Svelte ist das nicht vorgesehen, weil:

  • Svelte-Komponenten sind Funktionen — sie haben keine sinnvolle Vererbungs-Hierarchie.
  • Spezialisierung wird über Props ausgedrückt — eine Variant-Prop ersetzt sechs Sub-Klassen.
  • Verschachtelung über children ist ausdrucksstärker als jede Vererbung.

Spezialisierung über Props

Eine generische Komponente kennt mehrere Varianten:

svelte Button.svelte
<script>
    let { variant = 'default', children, ...rest } = $props();
</script>

<button class={`btn btn-${variant}`} {...rest}>
    {@render children()}
</button>

<style>
    .btn { padding: 0.5em 1em; border: none; border-radius: 4px; }
    .btn-default { background: #eee; }
    .btn-primary { background: #3367d6; color: white; }
    .btn-danger { background: #d33; color: white; }
</style>
svelte Verwendung
<Button>Standard</Button>
<Button variant="primary">Speichern</Button>
<Button variant="danger">Löschen</Button>

Statt class PrimaryButton extends Button gibt es eine Komponente, die per Prop konfiguriert wird.

Komposition über children

Die zweite, oft elegantere Form ist die Verschachtelung über die children-Snippet-Prop. Eine generische Hülle weiß nicht, was sie umschließt:

svelte PageLayout.svelte
<script>
    let { children } = $props();
</script>

<div class="page">
    <Header />
    <main>{@render children()}</main>
    <Footer />
</div>
svelte Spezielle Seite — keine Vererbung
<script>
    let { user } = $props();
</script>

<PageLayout>
    <h1>{user.name}</h1>
    <p>{user.bio}</p>
</PageLayout>

Mehrere benannte Slots

Wenn eine Komponente mehrere Inhaltsbereiche hat, kommen Snippet-Props zum Einsatz:

svelte SplitPanel.svelte
<script>
    let { left, right } = $props();
</script>

<div class="split">
    <aside>{@render left()}</aside>
    <section>{@render right()}</section>
</div>
svelte Verwendung
<SplitPanel>
    {#snippet left()}
        <Sidebar />
    {/snippet}
    {#snippet right()}
        <MainContent />
    {/snippet}
</SplitPanel>

Container / Presentational

Ein klassisches Komposition-Pattern: Eine Presentational-Komponente ist „dumm” und bekommt alle Daten als Props. Eine Container-Komponente kümmert sich um State, Datenladen und Side Effects:

svelte ProductList.svelte (presentational)
<script>
    let { products } = $props();
</script>

<ul>
    {#each products as product (product.id)}
        <li>{product.name}</li>
    {/each}
</ul>
svelte ProductListContainer.svelte
<script>
    import ProductList from './ProductList.svelte';
    import { useProducts } from '$lib/api/products.svelte';

    const { data: products = $state([]) } = useProducts();
</script>

<ProductList products={products} />

Vorteil: ProductList ist ohne Daten-Layer testbar und in mehreren Kontexten wiederverwendbar.

svelte Modal.svelte
<script>
    let {
        open = $bindable(false),
        title,
        footer,
        children,
    } = $props();

    let dialog;

    $effect(() => {
        if (open) dialog?.showModal();
        else dialog?.close();
    });
</script>

<dialog bind:this={dialog} onclose={() => open = false}>
    {#if title}<h2>{title}</h2>{/if}
    <div class="body">{@render children()}</div>
    {#if footer}<footer>{@render footer()}</footer>{/if}
</dialog>
svelte Verwendung
<script>
    let confirmDelete = $state(false);
</script>

<button onclick={() => confirmDelete = true}>Löschen</button>

<Modal
    bind:open={confirmDelete}
    title="Eintrag löschen?"
>
    <p>Möchtest du den Eintrag wirklich entfernen?</p>

    {#snippet footer()}
        <button onclick={() => confirmDelete = false}>Abbrechen</button>
        <button onclick={handleDelete}>Löschen</button>
    {/snippet}
</Modal>

Eine generische Modal-Komponente deckt damit alle möglichen Dialog-Varianten ab — ohne Vererbung.

Faustregeln

  • Erst Komposition versuchen. In den allermeisten Fällen kommst du damit sauber durch.
  • Logik in .svelte.ts-Helfern, Markup in .svelte-Komponenten.
  • Spezialisierte Komponenten als dünne Wrapper, die generischere Komponenten konfigurieren.
  • Keine extends-Hierarchien aufbauen. Tut Svelte ohnehin nicht.
  • Lieber zu klein als zu groß. Eine Komponente, die einen Satz beschreibt, ist meist gut geschnitten.

Häufige Anti-Patterns

Riesige Root-Komponenten. Wenn +page.svelte mehrere Hundert Zeilen umfasst, fehlt die Komposition. Aufteilen.

Zu viele Boolean-Props. <Button isPrimary isLarge isDisabled isLoading isRound /> — das schreit nach Variant-Props oder Aufteilung.

Slots/Snippets für Werte. Ein Snippet, das nur einen einzelnen String rendert, ist meistens eine Prop in Verkleidung.

Komposition als Selbstzweck. Drei verschachtelte Komponenten, die jeweils nur ein <div> rendern, sind keine Komposition — sondern Boilerplate.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu Components

Zur Übersicht