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
<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:
<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:
<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>:
<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:
<!-- 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.
<div class="card">Inhalt</div>
<style>
.card {
padding: 1rem;
border: 1px solid #ccc;
}
</style>Im Browser landet effektiv:
<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(...):
<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 tailwindcssergä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:
<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:
- Parsen in einen AST (Abstract Syntax Tree).
- Analyse: Welche Variablen sind reaktiv? Welche DOM-Knoten hängen von welchem Wert ab?
- Generieren: Spezifischer JavaScript-Code, der genau diese Knoten anfasst.
- 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:
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.