Seit Anbeginn der Zeit (in CSS-Begriffen) haben wir mit einer Kaskade in verschiedenen Bedeutungen gearbeitet. Unsere Stile bilden ein „Cascading Style Sheet“ (CSS). Und unsere Auswahlmöglichkeiten sind ebenfalls kaskadierend. Sie können auch seitlich ausgerichtet sein. In den meisten Fällen gehen sie nach unten. Aber niemals nach oben. Seit Jahren haben wir uns eine „Elternauswahl“ gewünscht. Und jetzt ist es endlich soweit! In Form eines :has()
-Pseudo-Selektors.
Die CSS-Pseudoklasse :has()
steht für ein Element, wenn einer der als Parameter übergebenen Selektoren mit mindestens einem Element übereinstimmt.
Es ist aber mehr als nur eine Auswahl für „übergeordnete Elemente“. Das ist eine gute Art, es zu bewerben. Die weniger ansprechende Möglichkeit ist die Auswahl „Bedingte Umgebung“. Aber das klingt nicht ganz so gut. Was ist mit der Auswahl „Familie“?
Unterstützte Browser
Bevor wir fortfahren, sollten wir noch kurz auf den Browsersupport eingehen. Es ist noch nicht ganz so weit. Aber es geht voran. Firefox wird noch nicht unterstützt, eine entsprechende Ausweitung ist aber geplant. Sie ist aber bereits in Safari verfügbar und wird in Chromium 105 veröffentlicht. Bei allen Demos in diesem Artikel wird angegeben, ob sie im verwendeten Browser nicht unterstützt werden.
Verwendung von :has
Wie sieht das aus? Betrachten Sie das folgende HTML-Beispiel mit zwei übergeordneten Elementen mit der Klasse everybody
. Wie wählen Sie den Knoten aus, der einen Nachkommen mit der Klasse a-good-time
hat?
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
Mit :has()
können Sie das mit dem folgenden CSS tun.
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
Dadurch wird die erste Instanz von .everybody
ausgewählt und ein animation
angewendet.
In diesem Beispiel ist das Element mit der Klasse everybody
das Ziel. Die Bedingung ist, dass ein Nachkomme mit der Klasse a-good-time
vorhanden ist.
<target>:has(<condition>) { <styles> }
Sie können aber noch viel mehr damit machen, denn :has()
bietet viele Möglichkeiten. Selbst solche, die wahrscheinlich noch nicht entdeckt wurden. Hier sind einige Beispiele:
Wählen Sie figure
-Elemente mit einer direkten figcaption
aus.
css
figure:has(> figcaption) { ... }
anchor
s auswählen, die keinen direkten SVG-Abkömmling haben
css
a:not(:has(> svg)) { ... }
label
s auswählen, die ein direktes input
-Schwesterelement haben Es geht seitwärts!
css
label:has(+ input) { … }
article
s auswählen, bei denen ein untergeordneter img
keinen alt
-Text enthält
css
article:has(img:not([alt])) { … }
documentElement
auswählen, bei dem ein bestimmter Status im DOM vorhanden ist
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
Layoutcontainer mit einer ungeraden Anzahl von untergeordneten Elementen auswählen
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
Alle Elemente in einem Raster auswählen, die nicht den Mauszeiger erhalten haben
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
Container auswählen, der ein benutzerdefiniertes Element <todo-list>
enthält
css
main:has(todo-list) { ... }
Alle einzelnen a
in einem Absatz auswählen, die ein direktes übergeordnetes hr
-Element haben
css
p:has(+ hr) a:only-child { … }
article
auswählen, bei dem mehrere Bedingungen erfüllt sind
css
article:has(>h1):has(>h2) { … }
Kombinieren Sie diese Anweisungen. Wählen Sie ein article
aus, wenn auf einen Titel ein Untertitel folgt.
css
article:has(> h1 + h2) { … }
Wählen Sie das :root
aus, wenn interaktive Status ausgelöst werden.
css
:root:has(a:hover) { … }
Wählen Sie den Absatz aus, der auf ein figure
folgt, das kein figcaption
hat.
css
figure:not(:has(figcaption)) + p { … }
Welche interessanten Anwendungsfälle fallen Ihnen für :has()
ein? Das Faszinierende daran ist, dass Sie dazu ermutigt werden, Ihr mentales Modell zu durchbrechen. Sie denken: „Könnte ich diese Stile anders angehen?“
Beispiele
Sehen wir uns einige Beispiele an, wie wir sie verwenden könnten.
Karten
Demo für klassische Karten ansehen Wir können beliebige Informationen auf unserer Karte anzeigen, z. B. einen Titel, eine Untertitelung oder Medien. Hier ist die Basiskarte.
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
</li>
Was passiert, wenn Sie Medien einfügen möchten? Bei diesem Design könnte die Karte in zwei Spalten aufgeteilt werden. Zuvor können Sie eine neue Klasse erstellen, um dieses Verhalten darzustellen, z. B. card--with-media
oder card--two-columns
. Diese Klassennamen sind nicht nur schwer zu rufen, sondern auch schwer zu verwalten und zu merken.
Mit :has()
können Sie erkennen, dass sich Medien auf der Karte befinden, und entsprechend reagieren. Namen von Modifikatorklassen sind nicht erforderlich.
<li class="card">
<h2 class="card__title">
<a href="/article.html">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
</li>
Und Sie müssen es auch nicht dort lassen. Sie können dabei kreativ werden. Wie könnte sich eine Karte mit „Empfohlenen“ Inhalten in einem Layout anpassen? Mit diesem CSS-Code würde eine vorgestellte Karte die gesamte Breite des Layouts einnehmen und am Anfang eines Rasters platziert werden.
.card:has(.card__banner) {
grid-row: 1;
grid-column: 1 / -1;
max-inline-size: 100%;
grid-template-columns: 1fr 1fr;
border-left-width: var(--size-4);
}
Was passiert, wenn eine vorgestellte Karte mit einem Banner wackelt, um Aufmerksamkeit zu erregen?
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
<div class="card__banner"></div>
</li>
.card:has(.card__banner) {
--color: var(--green-3-hsl);
animation: wiggle 6s infinite;
}
So viele Möglichkeiten.
Formulare
Wie sieht es mit Formularen aus? Sie sind bekannt dafür, dass sie schwierig zu stylen sind. Ein Beispiel hierfür sind Eingaben und ihre Labels. Wie signalisieren wir beispielsweise, dass ein Feld gültig ist? Mit :has()
wird das viel einfacher. Wir können die entsprechenden Pseudoklassen für Formulare verwenden, z. B. :valid
und :invalid
.
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
</div>
label {
color: var(--color);
}
input {
border: 4px solid var(--color);
}
.form-group:has(:invalid) {
--color: var(--invalid);
}
.form-group:has(:focus) {
--color: var(--focus);
}
.form-group:has(:valid) {
--color: var(--valid);
}
.form-group:has(:placeholder-shown) {
--color: var(--blur);
}
Testen Sie das in diesem Beispiel: Geben Sie gültige und ungültige Werte ein und heben Sie den Fokus auf und wieder auf.
Mit :has()
können Sie auch die Fehlermeldung für ein Feld ein- und ausblenden. Fügen Sie der Feldgruppe „E-Mail“ eine Fehlermeldung hinzu.
<div class="form-group">
<label for="email" class="form-label">
Email
</label>
<div class="form-group__input">
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
<div class="form-group__error">Enter a valid email address</div>
</div>
</div>
Standardmäßig wird die Fehlermeldung ausgeblendet.
.form-group__error {
display: none;
}
Wenn das Feld jedoch :invalid
wird und nicht fokussiert ist, können Sie die Meldung ohne zusätzliche Klassennamen anzeigen.
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
Es gibt keinen Grund, warum Sie nicht einen geschmackvollen Hauch von Verspieltheit hinzufügen könnten, wenn Nutzer mit Ihrem Formular interagieren. Betrachten Sie dieses Beispiel: Achten Sie darauf, dass Sie einen gültigen Wert für die Mikrointeraktion eingeben. Bei einem Wert von :invalid
wackelt die Formulargruppe. Aber nur, wenn der Nutzer keine Bewegungseinstellungen hat.
Inhalt
Darauf sind wir bereits in den Codebeispielen eingegangen. Aber wie könnten Sie :has()
in Ihrem Dokumentfluss verwenden? Es liefert Ideen dazu, wie wir die Typografie beispielsweise um Medien herum gestalten könnten.
figure:not(:has(figcaption)) {
float: left;
margin: var(--size-fluid-2) var(--size-fluid-2) var(--size-fluid-2) 0;
}
figure:has(figcaption) {
width: 100%;
margin: var(--size-fluid-4) 0;
}
figure:has(figcaption) img {
width: 100%;
}
Dieses Beispiel enthält Zahlen. Wenn sie keine figcaption
haben, schweben sie im Inhalt. Wenn ein figcaption
vorhanden ist, nimmt es die gesamte Breite ein und erhält einen zusätzlichen Rand.
Auf Status reagieren
Wie wäre es, wenn Ihre Stile auf einen bestimmten Status in unserem Markup reagieren? Sehen wir uns ein Beispiel mit der „klassischen“ Navigationsleiste an. Wenn Sie eine Schaltfläche haben, mit der die Navigationsleiste geöffnet und geschlossen wird, wird möglicherweise das aria-expanded
-Attribut verwendet. Die entsprechenden Attribute können mit JavaScript aktualisiert werden. Wenn aria-expanded
= true
ist, verwenden Sie :has()
, um dies zu erkennen und die Stile für die Navigationsleiste zu aktualisieren. JavaScript erledigt seinen Teil und CSS kann mit diesen Informationen machen, was es will. Sie müssen das Markup nicht neu anordnen oder zusätzliche Klassennamen hinzufügen. Hinweis: Dies ist kein produktionsreifes Beispiel.
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
Kann :has helfen, Nutzerfehler zu vermeiden?
Was haben all diese Beispiele gemeinsam? Abgesehen davon, dass sie Möglichkeiten zur Verwendung von :has()
zeigen, musste bei keiner der Lösungen der Klassenname geändert werden. Beide haben neue Inhalte eingefügt und ein Attribut aktualisiert. Dies ist ein großer Vorteil von :has()
, da es dazu beitragen kann, Nutzerfehler zu vermeiden. Mit :has()
kann CSS die Anpassung an Änderungen im DOM übernehmen. Sie müssen keine Klassennamen in JavaScript jonglieren, was das Risiko von Entwicklerfehlern verringert. Uns ist es schon allen passiert, dass wir einen Tippfehler in einen Klassennamen gemacht haben und ihn dann in Object
-Suchanfragen behalten mussten.
Das ist ein interessanter Gedanke. Führt er zu einem saubereren Markup und weniger Code? Weniger JavaScript, da wir weniger JavaScript-Anpassungen vornehmen. Weniger HTML, da Klassen wie card card--has-media
nicht mehr benötigt werden
Über den Tellerrand schauen
Wie bereits erwähnt, werden Sie bei :has()
dazu angehalten, das mentale Modell zu durchbrechen. Es ist eine Gelegenheit, verschiedene Dinge auszuprobieren. Eine Möglichkeit, die Grenzen zu erweitern, besteht darin, Spielmechaniken nur mit CSS zu erstellen. Sie können beispielsweise eine schrittweise Mechanik mit Formularen und CSS erstellen.
<div class="step">
<label for="step--1">1</label>
<input id="step--1" type="checkbox" />
</div>
<div class="step">
<label for="step--2">2</label>
<input id="step--2" type="checkbox" />
</div>
.step:has(:checked), .step:first-of-type:has(:checked) {
--hue: 10;
opacity: 0.2;
}
.step:has(:checked) + .step:not(.step:has(:checked)) {
--hue: 210;
opacity: 1;
}
Das eröffnet interessante Möglichkeiten. So können Sie ein Formular mit Transformationen durchlaufen. Hinweis: Diese Demo lässt sich am besten in einem separaten Browsertab ansehen.
Und wie wäre es zum Spaß mit dem klassischen Buzz-Wire-Spiel? Mit :has()
ist es einfacher, die Mechanik zu erstellen. Wenn der Draht berührt wird, ist das Spiel vorbei. Ja, wir können einige dieser Spielmechaniken mithilfe von Kombinatoren (+
und ~
) erstellen. Mit :has()
lassen sich aber dieselben Ergebnisse erzielen, ohne interessante Markup-„Tricks“ verwenden zu müssen. Hinweis: Diese Demo lässt sich am besten in einem separaten Browsertab ansehen.
Sie werden diese nicht so bald in die Produktion einbinden, aber sie zeigen Möglichkeiten auf, wie Sie das Primitive verwenden können. So können Sie beispielsweise eine :has()
verketten.
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
Leistung und Einschränkungen
Was können Sie mit :has()
nicht tun? Bei :has()
gibt es einige Einschränkungen. Die Hauptprobleme sind Leistungseinbußen.
- Sie können eine
:has()
nicht:has()
. Sie können jedoch eine:has()
verketten.css :has(.a:has(.b)) { … }
- Keine Verwendung von Pseudoelementen innerhalb von
:has()
css :has(::after) { … } :has(::first-letter) { … }
- Verwendung von
:has()
in Pseudos auf zusammengesetzte Auswahlkriterien beschränkencss ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- Verwendung von
:has()
nach Pseudo-Elementcss ::part(foo):has(:focus) { … }
einschränken - Die Verwendung von
:visited
ist immer falsch.css :has(:visited) { … }
Aktuelle Leistungsmesswerte zu :has()
findest du in diesem Glitch. Vielen Dank an Byungwoo für diese Erkenntnisse und Details zur Implementierung.
Geschafft!
Mach dich bereit für :has()
. Erzählt es euren Freunden und teilt diesen Beitrag. Das wird unsere Herangehensweise an CSS revolutionieren.
Alle Demos sind in dieser CodePen-Sammlung verfügbar.