Sveltes Style-Scoping ist großartig — solange du innerhalb einer Komponente bleibst. Aber eine App lebt nicht in einer einzelnen Datei. Themes, Akzentfarben, Spacing-Skalen wollen geteilt werden. Die saubere Brücke dafür sind CSS Custom Properties (CSS-Variablen). Sie werden vom Browser vererbt und durchschneiden Sveltes Scoping ohne Tricks. Dieser Artikel zeigt, wie du sie in Svelte sinnvoll einsetzt — vom globalen Theme-Token bis zur per Komponente konfigurierten Einzelkachel.
Warum CSS-Variablen so gut zu Svelte passen
Sveltes Scoping setzt Klassennamen mit Hash-Suffix — das ist auf der Selektor-Ebene. CSS-Variablen leben aber auf der Kaskaden-Ebene: Sie werden vererbt. Wer im <html>-Element eine Variable definiert, kann sie an jedem Nachfahren auslesen, auch quer durch Komponenten-Grenzen.
:root {
--color-accent: teal;
--color-text: #1a1a1a;
--space-2: 0.5rem;
--space-4: 1rem;
--radius-md: 8px;
}<article>...</article>
<style>
article {
padding: var(--space-4);
color: var(--color-text);
border-radius: var(--radius-md);
}
</style>Die Komponente kennt die Variable nicht von Geburt an — sie holt sie sich beim Rendern aus dem Document-Stylesheet. Wenn :root die Definition liefert, funktioniert sie.
Das macht CSS-Variablen zur idealen Theme-Schnittstelle: zentral definiert, lokal verwendet.
Variablen pro Komponente überschreiben
Die Vererbungs-Mechanik wird interessant, wenn du lokal einen anderen Wert setzen willst — ohne den globalen Default zu verlieren.
<article class="card">
<slot />
</article>
<style>
.card {
/* Überschreibt --color-accent für sich selbst und alle Nachfahren */
--color-accent: crimson;
color: var(--color-accent);
}
</style>Jede CSS-Regel kann CSS-Variablen setzen, nicht nur lesen. Innerhalb dieser .card (und ihrer Kinder) liefert var(--color-accent) jetzt crimson. Außerhalb gilt weiter der globale Wert.
Damit kannst du eine Komponente sehr elegant variantengetrieben gestalten, ohne neue Klassen oder Selektor-Verschachtelungen.
CSS-Variablen von außen per style:
Bisher haben wir die Variable im CSS gesetzt. Manchmal soll sie aber dynamisch sein — etwa abhängig vom State der Anwendung. Dafür gibt es die style:-Direktive im Markup:
<script>
let { progress = 0 } = $props();
</script>
<div class="bar" style:--progress="{progress}%">
<div class="fill"></div>
</div>
<style>
.bar {
position: relative;
height: 8px;
background: var(--color-bg-elev);
border-radius: 4px;
overflow: hidden;
}
.fill {
position: absolute;
inset: 0;
width: var(--progress);
background: var(--color-accent);
transition: width 200ms ease;
}
</style>Was passiert hier:
style:--progress="{progress}%"setzt im Markup eine inline Custom Property auf dem<div>-Element.- Im CSS liest
.filldiese Variable mitvar(--progress). - Sobald sich
progressim Script ändert, aktualisiert Svelte die Inline-Style-Eigenschaft. Die Breite des Balkens animiert sanft mit (transition).
Ohne diese Variable müsstest du style="width: {progress}%" direkt am .fill setzen — was funktioniert, aber das Markup mit Layout-Logik mischt. Mit der Variable bleibt CSS sauber im Style-Block.
Der Trick mit --style-props und der Komponente
Eine besonders elegante Anwendung: Komponenten lassen sich mit CSS-Variablen konfigurieren, ohne dass jede Anpassung eine neue Prop braucht.
<script>
let { children } = $props();
</script>
<span class="pill">
{@render children()}
</span>
<style>
.pill {
display: inline-block;
padding: 0.25em 0.75em;
border-radius: 999px;
background: var(--pill-bg, #eef);
color: var(--pill-color, #224);
font-size: var(--pill-size, 0.875rem);
}
</style>Die Komponente bietet drei „Konfigurations-Variablen” mit Defaults an. Jeder Konsument kann sie überschreiben — entweder per CSS-Klasse außen oder per style:-Direktive direkt am Element:
<Pill>Standard</Pill>
<Pill style:--pill-bg="crimson" style:--pill-color="white">
Achtung
</Pill>
<Pill style:--pill-size="1.1rem">
Größer
</Pill>Der Vorteil: Die Komponenten-API bleibt klein (kein eigener color, bg, size-Prop), die Anpassbarkeit ist trotzdem da. Wer mehr will, fügt einfach eine neue Variable hinzu.
Theme-Wechsel: Light- und Dark-Mode
Das gleiche Prinzip funktioniert für Themes. Du definierst zwei Sätze von Variablen, jeder unter einem Selektor:
:root {
--color-bg: #ffffff;
--color-text: #1a1a1a;
--color-line: #e2e2e2;
}
:root[data-theme='dark'] {
--color-bg: #1a1a1a;
--color-text: #f5f5f5;
--color-line: #333;
}Beim Theme-Wechsel setzt die App das Attribut auf dem <html>-Element:
<script>
let theme = $state('light');
$effect(() => {
document.documentElement.dataset.theme = theme;
});
</script>
<button onclick={() => theme = theme === 'light' ? 'dark' : 'light'}>
{theme}
</button>Sobald data-theme="dark" gesetzt ist, gewinnt der zweite Variablen-Block. Alle Komponenten, die var(--color-bg) etc. nutzen, schalten automatisch um — ohne CSS-Klassen-Wechsel, ohne Komponenten neu zu rendern.
Häufige Stolperfallen
Variable in der Komponente definieren und außen erwarten.
Eine Variable im <style> einer Komponente vererbt sich nur an die Nachfahren des stylenden Elements. Wer sie außerhalb der Komponente lesen will, muss sie auf einem Eltern-Element (oder im globalen Stylesheet) definieren.
Default-Wert vergessen.
Wer var(--pill-bg) ohne Default schreibt und niemand setzt die Variable, bekommt leeren Hintergrund. Mit var(--pill-bg, #eef) hast du einen Fallback.
Tippfehler im Variablen-Namen.
Anders als bei Klassen warnt der Compiler hier nicht — eine falsch geschriebene Variable führt einfach zu nicht funktionierendem CSS. Wer es absichern will, definiert globale Defaults und nutzt im IDE die Auto-Vervollständigung über .css-Dateien.
style: mit komplexen Werten.
style:--ratio={0.5} setzt den Wert als String "0.5". Bei Längen-Werten musst du die Einheit manuell anhängen — style:--width="{px}px" oder per Template-String.
Berechnete Werte mit calc(...).
Funktioniert tadellos: width: calc(var(--progress) * 1%) falls die Variable als reine Zahl ankommt. Praktisch, wenn du die Einheit nicht im Markup mitsenden willst.