Manchmal weiß man erst zur Laufzeit, welche Komponente gerendert werden soll — bei Wizard-Schritten, Tab-Inhalten, Plugin-Strukturen oder Editor-Layouts. In Svelte 5 funktioniert das ohne <svelte:component> — eine Komponenten-Variable kann direkt als Tag verwendet werden, solange sie mit Großbuchstaben beginnt. Dieser Artikel zeigt die typischen Patterns und die Migration aus Svelte 4.
Die einfache Form
Eine Variable, die eine Komponente enthält, lässt sich direkt als Tag setzen — vorausgesetzt sie beginnt mit Großbuchstaben:
<script>
import TabHome from './TabHome.svelte';
import TabProfile from './TabProfile.svelte';
import TabSettings from './TabSettings.svelte';
let active = $state(TabHome);
</script>
<nav>
<button onclick={() => active = TabHome}>Home</button>
<button onclick={() => active = TabProfile}>Profil</button>
<button onclick={() => active = TabSettings}>Einstellungen</button>
</nav>
<active /><active /> rendert die Komponente, die aktuell in active gespeichert ist. Bei jedem Wechsel von active rendert Svelte die alte Komponente weg und die neue auf.
Mit Props
Dynamische Komponenten nehmen Props wie jeder andere Tag:
<script>
let { Component, ...rest } = $props();
</script>
<Component {...rest} />Eine generische Render-Hülle, die ihre eigentliche Komponente per Prop bekommt.
Map-basiertes Pattern
Wenn die Komponentenwahl auf einem String-Schlüssel basiert (z. B. type aus einem JSON), eignet sich eine Map:
<script>
import TextBlock from './blocks/TextBlock.svelte';
import ImageBlock from './blocks/ImageBlock.svelte';
import VideoBlock from './blocks/VideoBlock.svelte';
const components = {
text: TextBlock,
image: ImageBlock,
video: VideoBlock,
};
let { blocks } = $props();
</script>
{#each blocks as block (block.id)}
{@const Component = components[block.type]}
{#if Component}
<Component {...block} />
{:else}
<p>Unbekannter Block: {block.type}</p>
{/if}
{/each}Die Konstante muss mit Großbuchstaben beginnen, damit Svelte sie als Komponente erkennt — daher Component (nicht component).
Lazy-Loading dynamischer Komponenten
Mit dynamischem Import lassen sich Komponenten erst dann laden, wenn sie gebraucht werden — das spart initiale Bundle-Größe:
<script>
let Component = $state(null);
async function loadEditor() {
const mod = await import('$lib/components/HeavyEditor.svelte');
Component = mod.default;
}
</script>
{#if Component}
<Component />
{:else}
<button onclick={loadEditor}>Editor laden</button>
{/if}Auch eine Variante mit {#await}-Block:
<script>
let promise = $state(null);
function load() {
promise = import('$lib/components/HeavyEditor.svelte');
}
</script>
<button onclick={load}>Editor laden</button>
{#if promise}
{#await promise}
<p>lädt …</p>
{:then mod}
{@const Editor = mod.default}
<Editor />
{/await}
{/if}Migration von <svelte:component>
In Svelte 4 brauchte es ein spezielles Tag, um eine Komponente per Variable zu rendern:
<svelte:component this={CurrentTab} {...tabProps} />In Svelte 5 ist das überflüssig — direkt als Tag schreiben:
<CurrentTab {...tabProps} /><svelte:component> funktioniert weiterhin im Legacy-Modus, ist aber nicht mehr nötig.
Praxis-Beispiel: Multi-Step-Wizard
<script>
import StepIntro from './steps/StepIntro.svelte';
import StepDetails from './steps/StepDetails.svelte';
import StepConfirm from './steps/StepConfirm.svelte';
const steps = [
{ Component: StepIntro, title: 'Einstieg' },
{ Component: StepDetails, title: 'Details' },
{ Component: StepConfirm, title: 'Bestätigung' },
];
let stepIndex = $state(0);
let current = $derived(steps[stepIndex]);
let formData = $state({});
</script>
<header>
<h2>{current.title}</h2>
<progress value={stepIndex + 1} max={steps.length}></progress>
</header>
<current.Component bind:data={formData} />
<nav>
{#if stepIndex > 0}
<button onclick={() => stepIndex--}>Zurück</button>
{/if}
{#if stepIndex < steps.length - 1}
<button onclick={() => stepIndex++}>Weiter</button>
{/if}
</nav>Beachten: Beim Property-Zugriff bleibt der Compiler streng — nur Top-Level-Bezeichner mit Großbuchstaben werden erkannt. Bei current.Component muss daher das Property selbst mit Großbuchstaben geschrieben werden.
Häufige Stolperfallen
Variable kleingeschrieben.
<myComponent /> interpretiert Svelte als HTML-Element — kein Fehler, aber auch keine Komponente.
Property-Zugriff mit Kleinschreibung.
<current.component /> funktioniert nicht. <current.Component /> ja. Konvention also: das Property-Feld muss großgeschrieben sein.
Wechsel ohne State-Reset. Wenn beim Wechsel der Komponente State erhalten bleiben soll, muss er außerhalb der Komponente liegen — z. B. in einem Eltern-Komponent oder in einem Store. Andernfalls wird beim Komponenten-Wechsel die Instanz zerstört und mit ihr der State.
Inhalte nicht reagieren auf Wechsel.
Wenn Snippets oder Children verwendet werden, müssen sie ggf. key-basiert neu erstellt werden, damit Lifecycle-Hooks korrekt feuern.