Selektoren in CSS/Logische Pseudoklassen

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Bis jetzt hast du gelernt, wie man Elemente mit einfachen Selektoren auswählt, sie mit Kombinatoren verknüpft und sogar Pseudo-Elemente erzeugst. Was aber, wenn du mehrere Muster gleichzeitig abgleichen oder bestimmte Elemente ausschließen willst, ohne lange, sich wiederholende Selektoren zu schreiben?

Hier kommen die Pseudo-Klassen für „logische Kombinationen”[1] in CSS ins Spiel: :is(), :not() und :where(). Mit ihnen kann man Selektoren gruppieren, Elemente herausfiltern oder Stile breit anwenden, ohne die Spezifität mehr als nötig zu erhöhen.

In diesem Artikel zeigen wir, wie man diese neuen CSS-Selektoren anwendet und damit kürzeres, klareres CSS schreibt, das weniger fehleranfällig ist.

In CSS können mehrere Selektoren, die dieselben Deklarationen haben, in einer durch Kommas getrennten Selektor-Liste zusammengefasst werden. Vor und/oder nach dem Komma können Leerzeichen oder Zeilenumbrüche stehen.

Selektor-Listen
h1 { font-family: sans-serif }
h2 { font-family: sans-serif }
h3 { font-family: sans-serif }

// entspricht:

h1, h2, h3 { font-family: sans-serif }
Beachte: Die Äquivalenz ist in diesem Beispiel gegeben, da alle Selektoren gültige Selektoren sind. Wäre nur einer dieser Selektoren ungültig, wäre die gesamte Selektorenliste ungültig. Dies würde die Regel für alle drei Überschriftenelemente ungültig machen, während im ersten Fall nur eine der drei einzelnen Überschriftenregeln ungültig wäre.[2]

Dies kann es schwierig machen, CSS zu schreiben, das neue Selektoren verwendet und dennoch in älteren Browsern, die diese noch nicht kennen, korrekt funktioniert.


Selektoren verkürzen mit :is()

Die matches-any-Pseudoklasse :is() ist eine funktionale Pseudoklasse, die eine <forgiving-selector-list> als einziges Argument akzeptiert. Diese <forgiving-selector-list> analysiert stattdessen jeden Selektor in der Liste einzeln und ignoriert einfach diejenigen, die nicht analysiert werden können, sodass die verbleibenden Selektoren weiterhin verwendet werden können.[3]

Mit ihr können nun umfangreiche Regelsätze übersichtlich verkürzt werden:

Wordpress-CSS - und verkürzt mit is()
.entry .entry-content blockquote,
.entry .entry-content figure,
.entry .entry-content p,
.entry .entry-content pre,
.entry .entry-content ul,
.entry .entry-content ol,
.entry .entry-content h1,
.entry .entry-content h2 {
  max-width: 52rem;
  margin-left:auto;
  margin-right: auto;
}

.entry .entry-content :is(blockquote, figure, p, pre, ul, ol, h1, h2) {
	max-width: 50em;
	margin-inline:auto;
}

Selektoren, die innerhalb der :is()-Pseudoklasse angegeben werden, werden in der Spezifität des Selektors berücksichtigt. Wenn man das nicht wünscht, kann statt dessen die :where()-Pseudoklasse verwendet werden, die sich im Übrigen identisch zu :is() verhält.

Hinweis:
Nicht jeder CSS-Parser akzeptiert alle Typen von Selektoren innerhalb der Selektoren-Liste. Bei Verwendung von :is() wird empfohlen sich auf einfache Selektoren zu beschränken und keine Kombinatoren bzw. komplexe Selektoren zu verwenden.
möglich, aber komplex in der späteren Wartung
article :is(header, footer) > p {  }
Beachte: :is() darf nicht in sich selbst verschachtelt werden; etwas wie is(:is(...)) ist somit nicht erlaubt. Ebenfalls nicht erlaubt ist die Verwendung der Pseudoklasse :not() innerhalb von :is(). Auch ist die Verwendung von Pseudo-Elementen generell nicht erlaubt.
:is(:link, :visited) { 
  color: red;
}

Das Beispiel soll die Schriftfarbe aller Verweise auf rot setzen, wird aber nicht ausgeführt. (Hinweis: Das Beispiel ist äquivalent zu any-link bzw. [href]).


Hinweis bei Verwendung von CSS-Namespaces: Die Selektoren innerhalb von :is() beachten einen definierten Standard-Namespace nur, wenn sie explizit einen Typ- oder Universalselektor enthalten. Ein mögliches Namespace-Präfix außerhalb des :is() gilt dann nicht innerhalb von :is()!

Universalselektor innerhalb und außerhalb von :is()
*|*:is(:hover, :focus) { color: red }
*|*:is(*:hover, *:focus) { color: red }

Im ersten Beispiel wird jedem überfahrenen oder fokussierten Element die Schriftfarbe rot zugewiesen, egal in welchem Namespace es sich befindet. Im zweiten Beispiel betrifft dies ausschließlich die Elemente im Standard-Namespace, da der Universalselektor innerhalb von :is explizit angegeben wurde.

Elemente ausschließen mit :not()

Während :is() die Auswahl eingrenzt, hilft :not(), Dinge auszuschließen, ohne wiederholten Code schreiben zu müssen. Die Pseudoklasse erwartet als Argument einen beliebigen Selektor. Sie spricht dann alle diejenigen Elemente an, auf die das Argument nicht zutrifft.

Stell dir vor, du gestaltest einen Blog. Du möchtest, dass der Großteil des Textes gleich aussieht – aber nicht alles. Überschriften müssen hervorstechen, Zitate benötigen einen besonderen Abstand, Bilder sollten vielleicht nicht dem gleichen Rhythmus wie Absätze folgen. Ohne :not() müsste man lange, sich wiederholende Regeln schreiben, um jeden Elementtyp einzeln abzudecken. Mit :not() kann man die Logik umkehren: Du gestaltest alles standardmäßig und gibst dann einfach an, was nicht gestaltet werden soll.

Alle außer den Überschriften
article :not(h1, h2, h3) {
  margin-top: 1em;
}

In diesem Beispiel würden alle Kindelemente des article, die keine Überschriften h1-h3 sind, einen oberen Rand von 1em erhalten. Dies ist etwa sinnvoll, wenn dieses Element neben Überschriften auch Absätze, Listen und Bilder enthält.


Kennzeichnung von Links ansehen …
  a:not([href*="example."]) { background: red; }

In diesem Beispiel werden alle Link-Elemente, deren href-Attribut nicht die Zeichenkette „example.“ enthält, mit einem roten Hintergrund versehen.

Zusammen sind wir startk: is() und not() kombiniert
:is(nav, aside) :not(a) {
  opacity: 0.6;
}

In Level 3 der Selektoren-Spezifikation durfte :not() nur einen einfachen Selektor enthalten, und :not() durfte nicht geschachtelt werden. Die meisten Browser unterstützen mittlerweile den Level 4, der auch Kombinatoren und Selektorenlisten zulässt und das Schachtelungsverbot aufhebt. Pseudoelemente (wie z. B. ::after) sind allerdings nicht zulässig.

Spezifität niedrig halten mit :where()

Die specificity-adjustment-Pseudoklasse :where() ist eine Variante der funktionalen Pseudoklasse :is(). Der einzige Unterschied ist, dass die Selektoren innerhalb von :where() nicht zur Spezifität des Gesamtselektors beitragen.

Ist doch das Gleiche, oder?
:is(h1, h2, h3) {
  margin-bottom: 0.5em;
}

:where(h1, h2, h3) {
  margin-bottom: 0.5em;
}

Sie sehen fast gleich aus, aber der große Unterschied liegt in der Spezifität.

  •  :is() → übernimmt die Spezifität des spezifischsten Selektors im Inneren.
  •  :where() → hat immer eine Spezifität von Null (wie ein universeller Selektor *).

Die Spezifität entscheidet, welche CSS-Regel Vorrang hat, wenn zwei Regeln dasselbe Element betreffen.

  • Verwende :is(), wenn du möchtest, dass sich der gruppierte Selektor normal verhält (einschließlich Spezifität).
  • Verwende :where(), wenn du Standardstile festlegen möchtest, die später leicht überschrieben werden können.


Eltern nach Kindern stylen mit :has()

Jahrelang konnte CSS nur den Element-Baum nach unten durchlaufen: Man konnte Kinder, Enkelkinder und Nachkommen stylen. Aber man konnte nie nach oben schauen – nie sagen: „Gib mir ein Elternteil, das ein bestimmtes Kind hat.“

Deshalb wirkt :has() fast wie Zauberei: Endlich kann CSS Beziehungen in beide Richtungen betrachten.

Denke an eine Navigationsleiste: Einige Menüpunkte sind einfache Links, andere öffnen ein Dropdown-Menü. Bisher musste man Klassen wie .has-dropdown in den HTML-Code einfügen, um diese unterschiedlich zu gestalten.

Mit :has() benötigt man diese zusätzlichen Klassen nicht mehr:

Beispiel
nav li:has(ul)::after {
  content: " ▾";
  font-size: 0.8em;
  color: #555;
}

Jedes li-Element, das als Kind eine weitere ul hat, wird mit einem Pfeil ausgestattet, damit Nutzer einen Hinweis auf die weitere Ebene haben.

Navigation/Dropdown-Menü


Bei Formularen musste man sich oft verrenken: Logischerweise sollte ein label vor dem Eingabefeld stehen, damit man es erst lesen und dann Daten eingeben kann. Um labels entsprechend der Eingabe zu gestalten, benötigte man den Geschwister-Kombinator, der aber nur nachfolgende Elemente selektierte.

Mit has() geht das leichter:

user-valid - erst wenn eine Eingabe erfolgt ansehen …
input:required:user-invalid {
  background-color: #fed8cd;
}
label:has(+ input:required)::after {
  content: " *";
  color: red;
  font-weight: bold;
}

label:has(+ input:user-valid)::after {
  content: " ✓";
  color: green;
  font-weight: bold;
}>

Die Pseudoklassen :user-valid, bzw. :user-invalid überprüfen nach einer Eingabe, ob diese valide ist. Hier wird das vorangestellte label mit einem ::after-Pseudoelement erweitert und so die (mangelnde) Korrektheit der Eingabe gekennzeichnet.

Formulare/Benutzereingaben zugänglich gestalten



Siehe auch

Dynamische Pseudoklassen selektieren Elemente aufgrund einer Benutzeraktion. Sie finden sich in vielen Bereichen und verrichten dort ihre Dienste:


  • CSS-Selektoren
    alphabetische Referenz für den schnellen Überblick
  • Links

    Maus- und Tastaturinteraktionen

    •  :hover und :focus
    •  :focus-within
    •  :visited, :link, :any-link
  • Akkordeon mit <details>
    •  :open
    •  :details-content
  • Formulare validieren
    •  :valid, :invalid
    •  :in-range, :out-of-range
  • Auswahllisten
    •  :checked, indeterminate
    •  :picker(select)
  • Ausgrauen: readonly vs disabled
    •  :disabled
    •  :read-only, :red-write


Weblinks

  1. 4. Logical Combinations Selectors Level 4
    W3C Working Draft, 11 November 2022
  2. 4. Selector Lists Selectors Level 4
    W3C Working Draft, 11 November 2022
  3. 18.1. <forgiving-selector-list> and <forgiving-relative-selector-list> Selectors Level 4
    W3C Working Draft, 11 November 2022