Wenn du einen Wert von einer Eltern-Komponente an ein Enkelkind durchreichen willst, brauchst du normalerweise eine Prop in jeder Zwischenstufe — auch wenn die nichts mit dem Wert zu tun hat. Das wird schnell hässlich. Sveltes Context-API ist die offizielle Lösung dafür: Eine Komponente legt einen Wert ab, beliebige Nachfahren holen ihn ab. Kein Prop-Durchreichen, keine externe Library, kein Store. Dieser Artikel zeigt das Grundprinzip.
Das Grundprinzip in zwei Funktionen
Die Context-API besteht aus genau zwei Funktionen, die du aus svelte importierst:
setContext(key, value)— eine Komponente legt einen Wert unter einem Schlüssel ab.getContext(key)— eine Nachfahr-Komponente holt den Wert mit demselben Schlüssel.
Wichtig zu verstehen: Der Context lebt am Komponenten-Baum, nicht global. Jede Komponente, die setContext aufruft, schafft einen eigenen Sichtbarkeitsbereich für ihre Nachfahren — Geschwister oder höher liegende Komponenten sehen nichts davon.
<script>
import { setContext } from 'svelte';
import Layout from './Layout.svelte';
setContext('locale', 'de-DE');
</script>
<Layout /><script>
import { getContext } from 'svelte';
let { date } = $props();
const locale = getContext('locale');
const formatted = new Intl.DateTimeFormat(locale).format(date);
</script>
<span>{formatted}</span>DateLabel muss nicht das direkte Kind von App sein — sie kann beliebig tief im Baum sitzen. Solange sie unterhalb der Komponente steht, die setContext('locale', ...) aufgerufen hat, bekommt sie den Wert.
Wann Context, wann Prop?
Die ehrliche Antwort: In den allermeisten Fällen reicht eine Prop. Context ist nicht der Standard — er ist die Ausnahme für Werte, die viele Komponenten in tiefen Bäumen brauchen.
Vier Situationen, in denen Context wirklich passt:
- Querschnitts-Werte wie das aktuelle Theme, die Locale, der angemeldete Nutzer.
- API-Clients oder Services, die unten im Baum benutzt werden, ohne dass jeder Zwischenschritt sie kennen will.
- Komponenten-Bibliotheks-APIs zwischen einer Eltern-Komponente und ihren spezifischen Kindern (z. B.
<Tabs>und<TabPanel>, die intern miteinander kommunizieren). - Form-Kontext: Eine
<Form>legt einen Validierungs-Helfer ab, ihre<Field>-Kinder holen ihn ab.
Wenn der Wert nur ein bis zwei Ebenen tief gebraucht wird oder nur in einer Komponente, ist eine Prop fast immer klarer. Context macht den Datenfluss unsichtbarer — was bei richtigem Einsatz angenehm, bei zu freizügigem Einsatz aber irritierend ist.
Schlüssel als String oder als Symbol
Im einfachen Beispiel oben war der Schlüssel ein String ('locale'). Das funktioniert, hat aber zwei Nachteile: Tippfehler werden zur Laufzeit zu silently failenden Lookups, und in einer App mit mehreren Bibliotheken können Schlüssel-Kollisionen entstehen.
Die robuste Variante: Ein Symbol als Schlüssel, exportiert aus einer zentralen Datei.
export const localeKey = Symbol('locale');<script>
import { setContext } from 'svelte';
import { localeKey } from '$lib/contexts/locale';
setContext(localeKey, 'de-DE');
</script><script>
import { getContext } from 'svelte';
import { localeKey } from '$lib/contexts/locale';
const locale = getContext(localeKey);
</script>Symbol-Schlüssel sind garantiert eindeutig — zwei Symbol-Aufrufe ergeben verschiedene Werte, selbst wenn die Beschreibung gleich ist. Tippfehler im Schlüssel-Namen werden zu Compiler-Fehlern statt stillen Lookups.
Wo wird der Context gesetzt?
setContext muss während des Setup einer Komponente aufgerufen werden — also direkt im <script>-Top-Level, nicht innerhalb eines Event-Handlers oder einer asynchronen Funktion. Der Aufruf wirkt einmalig beim Mount.
<script>
import { setContext } from 'svelte';
function onClick() {
setContext('foo', 'bar'); // funktioniert nicht zuverlässig
}
</script>
<button onclick={onClick}>Setzen</button><script>
import { setContext } from 'svelte';
setContext('foo', 'bar');
</script>Wenn sich der Wert über die Zeit ändern soll, gehst du nicht erneut über setContext, sondern legst einen reaktiven Container in den Context ab — siehe Folge-Artikel.
Type-Safety in TypeScript
Bei Strings oder Symbolen weiß TypeScript zunächst nicht, welcher Typ hinter dem Schlüssel steht. Wer Type-Safety will, baut sich kleine Wrapper.
import { setContext as set, getContext as get } from 'svelte';
const key = Symbol('locale');
export function setLocale(value: string) {
set(key, value);
}
export function getLocale(): string {
const value = get<string | undefined>(key);
if (value === undefined) {
throw new Error('locale-Context wurde nicht gesetzt');
}
return value;
}Die Komponenten benutzen jetzt nur die typisierten Wrapper, ohne sich um den Schlüssel oder den Cast zu kümmern:
<script>
import { setLocale } from '$lib/contexts/locale';
setLocale('de-DE');
</script><script>
import { getLocale } from '$lib/contexts/locale';
const locale = getLocale(); // string
</script>Ein zusätzlicher Vorteil: Wer den Context konsumiert, ohne dass eine Eltern-Komponente ihn gesetzt hat, bekommt einen eindeutigen Fehler — statt eines stummen undefined, das später schwer zu debuggen ist.
Häufige Stolperfallen
getContext außerhalb eines Komponenten-Setups.
getContext darf nur im Top-Level des <script> aufgerufen werden, nicht in Event-Handlern oder Async-Funktionen. Wer den Wert in einem Handler braucht, holt ihn im Setup ab und referenziert ihn dort.
Geschwister-Komponenten teilen sich den Context. Geschwister sehen den Context nicht, wenn er nur in einem von ihnen gesetzt wird. Der Context muss in einem gemeinsamen Vorfahren gesetzt werden, damit beide ihn sehen.
undefined zurückbekommen.
Wenn die Eltern-Komponente vergessen hat, setContext aufzurufen, gibt getContext undefined. Im Aufrufer einen Default oder einen Throw setzen — sonst hast du verzögerte Bugs.
Mehrere Aufrufe von setContext mit gleichem Schlüssel.
Der zweite Aufruf überschreibt den ersten. Das kann gewollt sein (eine Komponente überschreibt einen geerbten Wert), aber häufiger ist es Versehen — vor allem wenn Eltern und Großeltern beide setzen.
Context als globaler State missbraucht.
Context ist hierarchisch. Wenn du etwas wirklich global brauchst (Auth-Status, der von beliebigen Stellen aus änderbar sein soll), ist ein $state-Container in einer .svelte.ts-Datei meist die ehrlichere Lösung.