Scoped CSS & CSS Modules
Wie Vue Styles automatisch auf Komponenten begrenzt – und warum du nie wieder globale CSS-Konflikte haben wirst.
Warum Scoped Styles?
In großen Anwendungen ist CSS-Namenskollision ein häufiges Problem. Vue löst das elegant: Jede Komponente kann ihre eigenen Styles definieren, die nur für sie gelten. Kein BEM, kein CSS-in-JS – einfach normales CSS mit einem magischen Attribut.
So funktioniert Scoped CSS
Füge einfach das scoped-Attribut zum <style>-Block hinzu. Vue generiert ein eindeutiges Data-Attribut (z.B. data-v-7ba5bd90) und schreibt jeden Selektor so um, dass er nur Elemente mit diesem Attribut trifft.
<template>
<button class="btn">Klick mich</button>
</template>
<style scoped>
.btn {
background: #42b883;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
border: none;
cursor: pointer;
}
.btn:hover {
background: #33a06f;
}
</style>Was passiert unter der Haube?
Vue kompiliert .btn zu .btn[data-v-7ba5bd90]. Das HTML-Element bekommt automatisch data-v-7ba5bd90 hinzugefügt. So kann kein anderer .btn-Selektor aus einer anderen Komponente diese Styles überschreiben.
Deep Selectors mit :deep()
Manchmal musst du Styles in Child-Komponenten überschreiben – z.B. eine Bibliotheks-Komponente anpassen. Dafür gibt es :deep().
<style scoped>
/* Nur diese Komponente */
.wrapper {
padding: 1rem;
}
/* Greift auch in Kind-Komponenten */
.wrapper :deep(.child-class) {
color: red;
}
/* Kompiliert zu: .wrapper[data-v-xxx] .child-class */
</style>Sparsam einsetzen
:deep() durchbricht die Kapselung bewusst. Nutze es nur, wenn du Third-Party-Komponenten stylen musst. Für eigene Komponenten sind Props oder CSS-Custom-Properties die bessere Wahl.
:slotted() und :global()
Zwei weitere Pseudo-Selektoren für spezielle Fälle:
<style scoped>
/* Styles für Slot-Inhalte (vom Eltern eingefügt) */
:slotted(.highlight) {
background: yellow;
padding: 0.25rem;
}
/* Globaler Selektor innerhalb eines scoped Blocks */
:global(.app-wide-class) {
font-family: 'Inter', sans-serif;
}
</style>Wann :slotted() nutzen?
:slotted() ist perfekt für Layout-Komponenten, die Slot-Inhalte konsistent stylen wollen – z.B. Abstände zwischen eingefügten Elementen.
CSS Modules mit $style
Alternativ zu Scoped CSS kannst du CSS Modules verwenden. Dabei werden Klassennamen automatisch in eindeutige Hashes umgewandelt und als Objekt über $style verfügbar.
<template>
<div :class="$style.container">
<p :class="$style.text">Ich bin ein CSS Module!</p>
<p :class="[$style.text, $style.bold]">Mehrere Klassen</p>
</div>
</template>
<style module>
.container {
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
}
.text {
color: #42b883;
font-size: 1.125rem;
}
.bold {
font-weight: 700;
}
</style>Scoped vs. Modules – wann was?
Scoped CSS reicht für die meisten Fälle. Nutze CSS Modules, wenn du Klassennamen als JavaScript-Objekte brauchst – z.B. für dynamische Komposition oder wenn du den generierten Klassennamen im Script-Block referenzieren musst.
Reaktive Styles mit CSS v-bind()
Das Highlight: Mit v-bind() in CSS kannst du reaktive JavaScript-Werte direkt in CSS verwenden. Vue setzt sie als CSS-Custom-Properties auf dem Element.
<script setup>
import { ref } from 'vue'
const farbe = ref('#42b883')
const groesse = ref(16)
</script>
<template>
<div class="box">
<p>Dynamisch gestylt!</p>
<input type="color" v-model="farbe" />
<input type="range" v-model.number="groesse" min="12" max="32" />
</div>
</template>
<style scoped>
.box p {
color: v-bind(farbe);
font-size: v-bind(groesse + 'px');
transition: all 0.3s ease;
}
</style>Rails-Vergleich: Asset Pipeline vs. Vue Scoped CSS
Von Rails zu Vue
In Rails hast du die Asset Pipeline oder importmap für CSS. Styles sind global – du musst selbst mit BEM-Konventionen oder Namespacing arbeiten, um Konflikte zu vermeiden.
Vue macht das automatisch: Jede Komponente kapselt ihre Styles. Das ist vergleichbar mit dem Gedanken hinter ViewComponents mit eigenem CSS, aber nativ ins Framework integriert und zur Build-Zeit optimiert.
# Rails: Globales CSS, manuelles Namespacing
app/assets/stylesheets/components/_button.css
.btn-primary { ... } /* Hoffentlich kollidiert das nicht! */
# Vue: Automatisch gekapseltes CSS
components/Button.vue
<style scoped>
.btn { ... } /* Garantiert isoliert */
</style>Live-Demo: Dynamische Farben mit v-bind()
Dynamisch gestylte Karte
Alle Styles dieser Karte werden durch reaktive v-bind()-Werte gesteuert. Ändere die Regler oben und sieh die Änderungen in Echtzeit!
Tipp: Vue setzt intern CSS Custom Properties auf dem Element – z.B. --7ba5bd90-demoTextColor: #ffffff