Jede Svelte-Komponente lebt in einer einzigen Datei mit Endung .svelte. Sie kombiniert HTML, JavaScript/TypeScript und CSS in drei klar abgegrenzten Bereichen. Dieser Artikel zeigt im Detail, was in jeden Bereich gehört, welche Top-Level-Konstrukte es gibt, wie <script module> funktioniert, was scoped Styles bedeuten und wie die Datei vom Compiler verarbeitet wird.

Die drei Bereiche im Überblick

svelte Beispiel.svelte
<script>
    // Logik: Imports, Props, reaktive Werte, Funktionen
    let count = $state(0);
</script>

<!-- Markup: HTML mit Svelte-Syntax -->
<button onclick={() => count++}>
    Geklickt: {count}
</button>

<style>
    /* CSS — automatisch scoped */
    button {
        padding: 0.5em 1em;
    }
</style>

Die Reihenfolge der Bereiche ist konventionell <script> zuerst, dann das Markup, dann <style>. Technisch wäre auch eine andere Reihenfolge möglich — der Konvention zu folgen erleichtert das Lesen.

Alle drei Bereiche sind optional. Eine reine Layout-Komponente kann ohne <script>, ein reiner Logik-Helfer ohne Markup auskommen.

Der <script>-Bereich

Im <script>-Block stehen die Logik-Bestandteile der Komponente:

svelte Übersicht <script>
<script lang="ts">
    // Imports
    import { onMount } from 'svelte';
    import Greeting from '$lib/components/Greeting.svelte';

    // Props
    let { name = 'Welt' } = $props();

    // Reaktiver State
    let count = $state(0);

    // Abgeleitete Werte
    let doubled = $derived(count * 2);

    // Side Effects
    $effect(() => {
        console.log('count', count);
    });

    // Funktionen
    function reset() {
        count = 0;
    }

    // Lifecycle
    onMount(() => {
        console.log('Komponente gemountet');
    });
</script>

TypeScript

lang="ts" aktiviert TypeScript. SvelteKit-Setups mit TS-Auswahl haben das automatisch:

svelte TypeScript-Variante
<script lang="ts">
    type Props = {
        name?: string;
        onSave?: (value: string) => void;
    };

    let { name = 'Welt', onSave }: Props = $props();
</script>

Der spezielle <script module>-Block

Code, der nur einmal pro Modul (nicht pro Komponenten-Instanz) ausgeführt werden soll, gehört in <script module>:

svelte Counter.svelte
<script module>
    // Wird einmal pro Datei ausgeführt — geteilt von allen Instanzen
    let totalInstances = 0;

    export function getInstanceCount() {
        return totalInstances;
    }
</script>

<script>
    // Wird pro Instanz ausgeführt
    let count = $state(0);
</script>

<button onclick={() => count++}>{count}</button>

Anwendung typischerweise:

  • Geteilte Konstanten oder Caches (z. B. memoisierte Werte).
  • Benannte Exporte, die zusammen mit der Default-Komponente importiert werden sollen.
  • Modul-Scope-Variablen für Statistik oder Tracking.

In Svelte 4 hieß das Konstrukt <script context="module">. In Svelte 5 ist <script module> die kürzere und gültige Schreibweise.

Das Markup

Zwischen <script>/<script module> und <style> steht das Markup. Es ist HTML mit Svelte-Erweiterungen:

svelte Markup-Features
<!-- Werte einbinden -->
<p>Hallo, {name}!</p>

<!-- Bedingung -->
{#if isLoggedIn}
    <p>Willkommen zurück</p>
{:else}
    <p>Bitte einloggen</p>
{/if}

<!-- Schleife -->
<ul>
    {#each items as item (item.id)}
        <li>{item.name}</li>
    {/each}
</ul>

<!-- Promise -->
{#await fetchUser()}
    <p>lädt …</p>
{:then user}
    <p>{user.name}</p>
{:catch error}
    <p style="color: red">{error.message}</p>
{/await}

<!-- Komponente verwenden -->
<Greeting name={name} />

<!-- Snippet -->
{#snippet badge(label)}
    <span class="badge">{label}</span>
{/snippet}
{@render badge('Neu')}

Tieferes zu jedem Block im Kapitel Template-Syntax.

Komponenten-Tags vs. HTML-Tags

  • Lowercase (<button>, <div>) ⇒ HTML-Element.
  • Capitalized (<Greeting>) ⇒ Svelte-Komponente. Muss im <script> importiert sein.

Der <style>-Bereich – scoped per Default

Styles in einer .svelte-Datei sind automatisch auf die Komponente begrenzt. Der Compiler vergibt einen einzigartigen Klassennamen (z. B. svelte-abc123) und schreibt ihn in die Selektoren und in das Markup.

svelte Card.svelte
<div class="card">Inhalt</div>

<style>
    .card {
        padding: 1rem;
        border: 1px solid #ccc;
    }
</style>

Im Browser landet effektiv:

html Generiertes HTML & CSS
<div class="card svelte-abc123">Inhalt</div>

<style>
    .card.svelte-abc123 {
        padding: 1rem;
        border: 1px solid #ccc;
    }
</style>

Das verhindert versehentliche Kollisionen mit globalen Styles oder anderen Komponenten.

Globale Styles bewusst opt-in: :global()

Wenn ein Selector über die Komponente hinausgreifen soll, gibt es :global(...):

svelte :global() für Kindelemente
<style>
    /* Wirkt nur auf .card-Elemente in dieser Komponente */
    .card { ... }

    /* Wirkt auf jedes h2 innerhalb von .card — auch in Kind-Komponenten */
    .card :global(h2) {
        color: teal;
    }

    /* Vollständig globale Regel */
    :global(body) {
        margin: 0;
    }
</style>

Andere Style-Optionen

  • CSS Variablen lassen sich von außen über style:--name={...} setzen — vertieft im Artikel Styling.
  • Tailwind lässt sich per npx sv add tailwindcss ergänzen.
  • Externe CSS-Dateien importieren via import './styles.css'; im <script> — typischerweise im Layout.

Spezielle Top-Level-Konstrukte im Markup

Svelte erlaubt im Markup ein paar besondere Tags, die mit <svelte:...> beginnen:

  • <svelte:head> – schreibt Inhalte in <head> (Title, Meta-Tags).
  • <svelte:window> / <svelte:document> / <svelte:body> – Bindings und Events auf globale Objekte.
  • <svelte:options> – Compiler-Optionen pro Komponente (z. B. customElement, accessors).
  • <svelte:self> – die Komponente selbst (für Rekursion).
  • <svelte:boundary> (Svelte 5) – Error Boundary für eine Sub-Hierarchie.

Beispiel <svelte:head> für SEO:

svelte Seite mit Head-Daten
<script>
    let { title, description } = $props();
</script>

<svelte:head>
    <title>{title}</title>
    <meta name="description" content={description} />
</svelte:head>

<h1>{title}</h1>

Was passiert beim Compile?

Der Svelte-Compiler verarbeitet eine .svelte-Datei in mehreren Schritten:

  1. Parsen in einen AST (Abstract Syntax Tree).
  2. Analyse: Welche Variablen sind reaktiv? Welche DOM-Knoten hängen von welchem Wert ab?
  3. Generieren: Spezifischer JavaScript-Code, der genau diese Knoten anfasst.
  4. CSS-Verarbeitung: Selektoren werden mit eindeutigem Klassennamen versehen.

Das Ergebnis ist eine ESM-Modul-Datei, die als Default-Export eine Funktion exportiert (in Svelte 5 — keine Klasse mehr). Importiert wird sie wie ein normales ES-Modul:

TypeScript Import einer .svelte-Datei
import App from './App.svelte';

Häufige Stolperfallen

Mehrere <script>-Blöcke ohne module. Erlaubt sind genau ein <script>-Block (Instance-Scope) und optional ein <script module>-Block — nicht mehr.

Reaktivität in importierten .js-Dateien. Reines .js ist nicht reaktiv. Wer Runes außerhalb einer .svelte-Datei nutzen will, braucht eine .svelte.js oder .svelte.ts-Datei.

<style> mit globalen Selektoren ohne :global(). Selektoren, die in der Komponente nicht selbst verwendet werden, würden sonst vom Compiler als „unused” markiert und gelöscht.

Komponenten-Tag in Kleinschreibung. Wird als HTML-Element interpretiert und produziert keinen Fehler — sondern wirkt einfach „nicht”.

Zu viel Logik im Markup. Komplexe Berechnungen besser in $derived oder Funktionen auslagern. Das Markup soll lesbar bleiben.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu Grundlagen

Zur Übersicht