Eine der angenehmsten Eigenschaften von Svelte ist, dass CSS in einer Komponente standardmäßig nur in dieser Komponente wirkt. Du brauchst keine BEM-Konvention, keine CSS-Modules, keine CSS-in-JS-Library — eine .svelte-Datei mit <style>-Block reicht. Der Trick: Beim Compilen vergibt Svelte einen eindeutigen Klassennamen und schreibt ihn an die Selektoren und ans Markup. Was nach Magie aussieht, ist im Browser ganz normales CSS.

Wie Scoping wirklich funktioniert

Schau dir diese minimal-Komponente an:

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

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

Der Compiler nimmt diese Datei und erzeugt daraus etwas, das im Browser so aussieht:

html Was im Browser ankommt
<div class="card svelte-abc123">Inhalt</div>

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

Drei Dinge sind dabei passiert:

  1. Der Compiler hat einen eindeutigen Klassennamen generiert (svelte-abc123 — der Hash wird aus dem Komponenten-Inhalt abgeleitet).
  2. Er hat diesen Namen ans Markup-Element angehängt.
  3. Er hat den CSS-Selektor um den gleichen Namen erweitert: aus .card wird .card.svelte-abc123.

Eine andere Komponente in der App, die ebenfalls eine .card-Klasse stylt, bekommt einen anderen Hash. Beide Selektoren existieren parallel — sie kollidieren nicht, weil die kombinierte Spezifität sie unterscheidet.

Das ganze Scoping läuft also ohne JavaScript-Runtime, ohne Shadow-DOM und ohne CSS-in-JS. Es ist normales CSS, das vom Compiler gezielt eingegrenzt wurde.

Was passiert mit ungenutzten Selektoren?

Eine Eigenheit, die viele anfangs überrascht: Der Compiler entfernt Selektoren, die er im Markup nicht findet.

svelte Ungenutzter Selektor
<div class="card">Inhalt</div>

<style>
    .card { padding: 1rem; }
    .does-not-exist { color: red; } /* wird im Build entfernt */
</style>

Der Compiler analysiert das Markup, findet keinen Treffer für .does-not-exist und lässt diesen Selektor weg — plus eine Compiler-Warnung. Das hält das CSS klein und zwingt zur Disziplin.

Was aber, wenn die Klasse durch Kind-Komponenten kommt oder durch eine Drittanbieter-Library gesetzt wird, also außerhalb der Datei? Dafür gibt es :global().

:global() – wenn Scoping zu eng ist

Manche Stile sollen sich gezielt nicht auf die Komponente begrenzen. Drei typische Fälle:

  • Du willst ein Element stylen, das zwar im Markup deiner Komponente steht, aber von einer Kind-Komponente gerendert wird.
  • Eine Drittanbieter-Library fügt Klassen am DOM hinzu, die du in deiner Komponente styling-mäßig erwischen willst.
  • Du brauchst einen wirklich globalen Style (z. B. body, html, eine Reset-Regel).

Mit :global(...) umschließt du den Teil des Selektors, der vom Scoping ausgenommen bleiben soll.

Globale Regel innerhalb der Komponente

svelte Komplett globale Regel
<style>
    :global(body) {
        margin: 0;
        font-family: system-ui;
    }
</style>

Der Selektor body bleibt unverändert. Diese Regel wird in den globalen Stylesheet aufgenommen, ohne Hash-Suffix.

Innerhalb der eigenen Komponente, aber für Kinder

svelte Selektive Tiefe
<article class="card">
    <slot />
</article>

<style>
    .card { padding: 1rem; }

    /* Wirkt auf jedes h2 INNERHALB von .card der eigenen Komponente,
       auch wenn das h2 von einer Kind-Komponente gerendert wird */
    .card :global(h2) {
        color: teal;
    }
</style>

Wichtig: .card selbst behält das Scoping (es muss in dieser Komponente existieren), nur h2 ist global. Damit holst du dir Stile in Kind-Komponenten, ohne auf das Scoping in der eigenen Komponente zu verzichten.

Globaler Block für mehrere Selektoren

Seit einer Weile akzeptiert Svelte auch eine größere Form, die einen kompletten Block markiert:

svelte :global { ... }
<style>
    :global {
        body {
            margin: 0;
        }
        h1 {
            font-weight: 700;
        }
    }
</style>

Das ist praktisch, wenn mehrere globale Regeln zusammengehören.

Globale Stylesheets sind weiterhin erlaubt

Manchmal will man Reset, Schriftarten oder Design-Token in einer echten CSS-Datei pflegen — ohne sie in eine Komponente zu legen. Das geht ganz normal: Eine CSS-Datei importieren, etwa im Layout der App.

svelte src/routes/+layout.svelte
<script>
    import '../app.css';
    let { children } = $props();
</script>

{@render children()}

app.css enthält dann zum Beispiel:

css src/app.css
:root {
    --font-main: 'Inter', sans-serif;
    --color-accent: teal;
}

body {
    margin: 0;
    font-family: var(--font-main);
}

Solche Dateien laufen außerhalb des Scopings und können von jeder Komponente konsumiert werden — meistens über CSS-Variablen, die im <style>-Block der Komponente referenziert werden.

Praktisches Beispiel: Card mit Theme-Token

So sieht eine typische Komponente aus, die globale Variablen nutzt und ihren eigenen Scope hat:

svelte Card.svelte
<script>
    let { title, children } = $props();
</script>

<article class="card">
    <h2 class="title">{title}</h2>
    <div class="body">
        {@render children()}
    </div>
</article>

<style>
    .card {
        padding: var(--space-4);
        border: 1px solid var(--color-line);
        border-radius: var(--radius-md);
        background: var(--color-bg);
    }

    .title {
        font-size: 1.25rem;
        margin: 0 0 var(--space-2);
        color: var(--color-text);
    }

    .body {
        color: var(--color-text-dim);
        line-height: 1.5;
    }
</style>

Drei Dinge zu beobachten:

  • Die Selektoren .card, .title, .body sind lokal zur Komponente. Eine andere Komponente mit .title ist nicht betroffen.
  • Die CSS-Variablen wie --space-4 kommen aus dem globalen Stylesheet. Sie funktionieren über das Scoping hinweg, weil CSS-Variablen vererbt werden.
  • Es gibt keinen einzigen :global-Aufruf. In den meisten Komponenten brauchst du das auch gar nicht.

Häufige Stolperfallen

Selektor wirkt nicht — oft fehlt nur die Klasse. Der Compiler entfernt Selektoren, deren Klassen im Markup nicht vorkommen. Tippfehler in einem Klassennamen (im Style oder im Markup) führen dazu, dass der Selektor weg-optimiert wird. Die Compiler-Warnung im Terminal zeigt das.

Style aus der Komponente erreicht ein Kind-Element nicht. Erwartet — das Scoping ist Absicht. Mit :global(selector) öffnest du gezielt nur den Teil, der durchschlagen soll. Pauschal alles global zu machen, ist meist das Gegenteil dessen, was du eigentlich willst.

:global einfach am Anfang setzen. :global { ... } macht den ganzen darin liegenden Block global. Das kann gewollt sein — oder eine Falle, wenn man es vergisst und plötzlich überall im DOM Effekte sieht.

Kombinator über Komponenten-Grenze. .parent > .child funktioniert nur, wenn beide Klassen in derselben Komponente vergeben werden. Liefert eine Kind-Komponente das .child, brauchst du .parent :global(.child).

Hot-Reload zeigt alte Stile. In sehr seltenen Fällen verheddert sich der Vite-Cache. Strg+R mit deaktiviertem Cache oder einmal npm run dev neu starten löst es.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu Styling

Zur Übersicht