S SvelteKurs
M1 · Svelte-Komponenten

Scoped CSS & :global

Styles in Svelte gelten standardmäßig nur für ihre eigene Komponente. Kein Ausufern, keine Klassenkonflikte — und wenn du globale Styles willst, sagst du es explizit.

Styles sind standardmäßig scoped

Das <style> einer Komponente gilt nur für diese Komponente. Schreibst du eine Regel für h1, betrifft sie ausschließlich die <h1>-Elemente in dieser Datei — nicht die in anderen Komponenten.

Titel.svelte svelte
<h1>Titel dieser Komponente</h1>

<style>
  /* Gilt NUR für <h1> in DIESER Datei.
     Svelte hängt automatisch eine eindeutige Klasse an,
     z.B. <h1 class="s-a1b2c3">. */
  h1 {
    color: rebeccapurple;
  }
</style>

Wie funktioniert das? Der Compiler vergibt pro Komponente eine eindeutige Klasse und hängt sie an die betroffenen Elemente und Selektoren an. Aus deiner einfachen Regel wird also etwas Spezifisches:

html
<!-- Svelte erzeugt aus dem obigen Markup ungefähr das: -->
<h1 class="svelte-a1b2c3">Titel dieser Komponente</h1>

<style>
  h1.svelte-a1b2c3 { color: rebeccapurple; }
</style>

ℹ️ Du musst dir nichts merken

Das Anhängen der Klasse passiert vollautomatisch zur Build-Zeit. Du schreibst ganz normales CSS — Svelte sorgt dafür, dass es dort bleibt, wo es hingehört.

Rails-Vergleich: der globale Namensraum

🛤️ Asset Pipeline kennt kein Scoping

In Rails werden alle Stylesheets über die Asset Pipeline (oder Propshaft/Sprockets) zu einem großen, globalen Stylesheet zusammengefügt. Eine .card-Regel aus users.css und eine gleichnamige aus products.css landen im selben Namensraum und überschreiben sich gegenseitig — je nach Ladereihenfolge.

Asset Pipeline css
/* app/assets/stylesheets/application.css */
/* Alle Stylesheets landen im selben globalen Namensraum. */

/* users.css */
.card { border: 1px solid #ccc; }

/* products.css — ÜBERSCHREIBT versehentlich obiges .card! */
.card { border: 2px solid red; }

/* Keine Kapselung → Konflikte, BEM/Namespacing als Notlösung */

Deshalb greifen Rails-Teams zu Konventionen wie BEM oder Namespacing, um Kollisionen zu vermeiden. In Svelte ist das überflüssig: Jede Komponente hat ihren eigenen CSS-Namensraum. Du darfst überall .card heißen, ohne dass es kracht.

Bewusst global werden: :global(...)

Manchmal willst du, dass eine Regel über die Komponentengrenze hinaus wirkt — etwa für Basis-Styles oder für Markup, das du nicht selbst kontrollierst. Dann wickelst du den Selektor in :global(...):

svelte
<style>
  /* Bricht das Scoping bewusst auf — gilt app-weit: */
  :global(body) {
    margin: 0;
    font-family: system-ui;
  }

  /* Auch nur für Kinder DIESER Komponente,
     aber inkl. dynamisch eingefügtem Markup: */
  .inhalt :global(a) {
    color: var(--brand);
  }
</style>

Für wirklich app-weite Grundlagen (Reset, Schriftarten, CSS-Variablen) nutzt du außerdem ein klassisches globales Stylesheet, das du typischerweise im Root-Layout importierst:

+layout.svelte ts
// src/routes/+layout.svelte oder ein Import in +layout
import '../app.css'; // globale Basis-Styles, Reset, CSS-Variablen

⚠️ Sparsam einsetzen

:global() ist die bewusste Ausnahme, nicht die Regel. Jede globale Regel bringt genau die Konflikte zurück, die Scoping eigentlich verhindert. Nutze es gezielt für Basis-Styles — nicht als Standard.

Klassen dynamisch: die class:-Direktive

Um eine Klasse abhängig von einer Bedingung zu setzen, brauchst du kein String-Gebastel. Die class:name={bedingung}-Direktive fügt die Klasse hinzu, wenn die Bedingung true ist, und entfernt sie sonst:

Toggle.svelte svelte
<script lang="ts">
  let aktiv = $state(false);
</script>

<!-- class:name={bedingung} — Klasse "aktiv" nur wenn true -->
<button class:aktiv onclick={() => (aktiv = !aktiv)}>
  Umschalten
</button>

<!-- Kurzform: class:aktiv ist gleichbedeutend mit class:aktiv={aktiv} -->
<!-- Mehrere gleichzeitig: -->
<div class:aktiv class:gross={istGross}>…</div>

<style>
  .aktiv { background: var(--brand); color: white; }
  .gross { font-size: 2rem; }
</style>

Steht die Klasse gleich der Variable, gibt es die Kurzform: class:aktiv ist dasselbe wie class:aktiv={aktiv}. Mehrere class:-Direktiven am selben Element sind problemlos möglich.

Einzelne Eigenschaften: die style:-Direktive

Für einzelne, dynamisch berechnete CSS-Eigenschaften gibt es das Gegenstück style:prop={wert}. Das ist lesbarer als ein zusammengesetzter style-String und ideal für Werte, die aus dem Zustand kommen:

svelte
<script lang="ts">
  let farbe = $state('#ff3e00');
</script>

<!-- style:prop={wert} setzt eine einzelne CSS-Eigenschaft dynamisch -->
<p style:color={farbe}>Farbiger Text</p>

<!-- Mehrere & mit Einheiten: -->
<div style:width="{breite}px" style:opacity={0.5}>…</div>

Live ausprobieren

Der Button unten nutzt zwei class:-Direktiven und die style:-Direktive für die Farbe. Alles reagiert live auf $state:

▶ class: & style: in Aktion · Interaktiv

Der Code dahinter — reines class: plus ein bisschen scoped CSS:

Toggle.svelte svelte
<script lang="ts">
  let aktiv = $state(false);
  let gross = $state(false);
</script>

<button
  class:aktiv
  class:gross
  onclick={() => (aktiv = !aktiv)}
>
  {aktiv ? 'Aktiv!' : 'Klick mich'}
</button>

<style>
  button { transition: all 0.2s; }
  .aktiv { background: #ff3e00; color: white; }
  .gross { transform: scale(1.25); }
</style>

Zusammenfassung

💡 Das Wichtigste

  • CSS in einer Komponente ist automatisch scoped — keine Konflikte.
  • :global(...) durchbricht das Scoping bewusst; sparsam einsetzen.
  • App-weite Basis-Styles kommen aus einem globalen Stylesheet (z. B. im Layout).
  • class:name={bedingung} schaltet Klassen reaktiv; Kurzform class:name.
  • style:prop={wert} setzt einzelne CSS-Eigenschaften dynamisch.

🎯 Probier es selbst

Füge dem Demo-Button eine dritte class:-Direktive hinzu (z. B. class:demo-rund), die ihn per Checkbox stark abrundet (border-radius: 9999px). Beobachte, wie sich Klassen unabhängig voneinander an- und abschalten lassen.