Zum Inhalt springen

Modernes CSS

CSS hat sich in den letzten Jahren stärker verändert als im Jahrzehnt davor. Dieses Kapitel zeigt drei Neuerungen, die den Alltag spürbar einfacher machen, und einen Blick nach vorn.

  • mit :has() ein Element abhängig von seinem Inhalt gestalten
  • CSS übersichtlich verschachteln
  • einen Dunkelmodus mit wenigen Zeilen bauen
  • neuere Funktionen richtig einordnen

Live-Coding zum Mitmachen

⤓ demo-20-modernes-css.zip herunterladen

Der Ordner starter ist der Ausgangsstand, fertig der Stand nach dem Live-Coding.

Enthält: index.html, stil.css

Bisher konnte CSS nur nach unten schauen. :has() wählt ein Element danach aus, was es enthält. Man nennt es auch den Elternselektor.

Die Karte reagiert auf ihre Checkbox
.karte { margin-bottom: 0.5rem; padding: 0.5rem; background: #e0e7ff; }
.karte:has(input:checked) { background: #dcfce7; text-decoration: line-through; }
form:has(:invalid) button { opacity: 0.5; }
<div class="karte"><label><input type="checkbox"> Kreuze mich an: Die Karte reagiert</label></div>
<div class="karte"><label><input type="checkbox"> Mich auch</label></div>
.karte:has(input:checked) { … } /* Karte mit angekreuzter Checkbox */
.spalte:has(.neu) { … } /* Spalte, in der eine neue Karte liegt */
form:has(:invalid) button { … } /* Button in einem Formular mit Fehlern */
h2:has(+ p) { … } /* h2, auf die direkt ein Absatz folgt */

Vieles davon brauchte früher JavaScript.

Zwei Verwandte: :is(h1, h2, h3) a kürzt Selektorlisten ab, und :where() tut dasselbe, ohne Spezifität hinzuzufügen.

Zusammengehörige Regeln dürfen ineinander stehen. Das & steht für den äußeren Selektor.

Verschachtelte Regeln
.karte {
padding: 0.5rem;
background: #e0e7ff;
& h4 {
margin: 0;
color: #312e81;
}
&:hover {
background: #c7d2fe;
}
@media (min-width: 30rem) {
padding: 1rem;
}
}
<div class="karte"><h4>Verschachtelt</h4>Fahre mit der Maus darüber.</div>

Verschachtle höchstens zwei bis drei Ebenen. Jede Ebene erhöht die Spezifität, und tiefe Nester lassen sich schlecht überschreiben.

Dank Custom Properties ist er beinahe geschenkt: Du tauschst die Variablen, und alles andere folgt.

Folgt deinem System
:root { --grund: #fff; --text: #1c2330; --akzent: #4338ca; }
@media (prefers-color-scheme: dark) {
:root { color-scheme: dark; --grund: #12131c; --text: #e6e8ef; --akzent: #818cf8; }
}
body { background: var(--grund); color: var(--text); }
a { color: var(--akzent); }
<p>Stelle dein System auf hell oder dunkel, und diese Fläche folgt. <a href="#">Auch die Links</a>.</p>
  • color-scheme: dark färbt auch Bildlaufleisten und Formularelemente passend ein.
  • Prüfe den Kontrast in beiden Modi neu. Eine Farbe, die auf Weiß funktioniert, muss das auf Schwarz nicht.
  • Nimm kein reines Schwarz und kein reines Weiß, weiche Töne strengen weniger an.
  • Testen: DevTools, Rendering, Emulation von prefers-color-scheme.

Ein Schalter auf der Seite braucht zusätzlich etwas JavaScript. Das ist eine Aufgabe „Für Schnelle“ im nächsten Kapitel.

Funktion Wofür Heute
Anchor Positioning Element an ein anderes heften, etwa für Tooltips mit @supports
Scroll-driven Animations Animation folgt dem Scrollen mit @supports
@scope Regeln auf einen Bereich begrenzen mit @supports
popover Aufklappendes ohne JavaScript prüfen, ob es die Zielgruppe hat
light-dark() zwei Farben in einem Wert für hell und dunkel bald überall sicher
@supports (anchor-name: --marke) {
.tooltip {
position: absolute;
position-anchor: --marke;
top: anchor(bottom);
}
}
stil.css
/* :has() – das Elternelement reagiert auf sein Kind */
.karte:has(input:checked) {
opacity: 0.6;
}
.karte:has(input:checked) h3 {
text-decoration: line-through;
}
.spalte:has(.neu) h2::after {
content: " •";
color: var(--farbe-akzent);
}
/* Nesting – zusammengehörige Regeln stehen zusammen */
.erledigt {
margin-top: var(--abstand-s);
font-size: 0.875rem;
& label {
display: inline-flex;
align-items: center;
gap: 0.4rem;
min-height: 2.75rem;
cursor: pointer;
}
& input {
width: 1.25rem;
height: 1.25rem;
accent-color: var(--farbe-akzent);
}
}
/* Dunkelmodus – nur die Variablen wechseln, alles andere folgt */
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--farbe-text: #e6e8ef;
--farbe-text-leise: #a4abba;
--farbe-akzent: #818cf8;
--farbe-akzent-dunkel: #1e1b4b;
--farbe-flaeche: #12131c;
--farbe-karte: #232533;
--farbe-linie: #3a3e52;
--farbe-spalte: #1a1c28;
}
.seitenleiste a[aria-current="page"],
.knopf:not(.leise),
.neu {
color: #12131c;
}
select {
background-color: var(--farbe-karte);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23e6e8ef'/%3E%3C/svg%3E");
color: var(--farbe-text);
}
a {
color: var(--farbe-akzent);
}
.seitenleiste a,
.reiter a {
color: inherit;
}
}

Starter für die Übung

⤓ uebung-20-modernes-css.zip herunterladen

ZIP-Datei entpacken und den Ordner in VS Code öffnen (Datei → Ordner öffnen).

Enthält: index.html, profil.html, stil.css, uebung.css

Die Seite profil.html enthält ein Formular. Schreibe deine Regeln in uebung.css.

Gestalte .profil und die enthaltenen Felder und den Knopf in einem verschachtelten Block.

Jedes .feld enthält eine versteckte .fehler-Meldung. Sie erscheint nur, wenn das Feld ausgefüllt, aber ungültig ist. Das leere placeholder=" " ist ein Kniff, mit dem du per :placeholder-shown erkennst, ob schon etwas eingetragen wurde.

Das Feld „Wie oft?“ erscheint nur, wenn der Newsletter angekreuzt ist, und zwar ohne JavaScript.

Prüfe die Seite im Dunkelmodus. Was stimmt nicht mehr? Korrigiere es.