Modul 5: Styling

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.

MeinButton.vue
<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().

ElternKomponente.vue
<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:

SlotContainer.vue
<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.

ModulBeispiel.vue
<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.

DynamischerStyle.vue
<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 vs. Vue
# 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()

CSS v-bind() Demo
Interaktiv

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