Seit Beginn (in Bezug auf CSS) haben wir in verschiedenen Bereichen mit einer Kaskade gearbeitet. Unsere Stile ergeben ein sogenanntes Cascading Style Sheet. Und unsere Selektoren kaskaden. Sie können seitlich gehen. In den meisten Fällen gehen sie nach unten. Aber nie nach oben. Wir denken schon seit Jahren über einen „Parent Selector“. Und jetzt ist es endlich so weit! In Form eines :has()
-Pseudoselektors.
Die CSS-Pseudoklasse :has()
stellt ein Element dar, wenn einer der als Parameter übergebenen Selektoren mit mindestens einem Element übereinstimmt.
Aber es ist mehr als nur ein Elternteil Selektor. Das ist eine schöne Möglichkeit, es zu vermarkten. Ein weniger ansprechender Weg ist vielleicht die „bedingte Umgebung“, Selektor. Aber das muss nicht so klingen. Wie wäre es mit der „Familie“? Selektor?
Unterstützte Browser
Bevor wir fortfahren, möchte ich noch auf die Browserunterstützung eingehen. Es ist noch nicht ganz so weit. Aber es rückt immer näher. Firefox wird noch nicht unterstützt, dies ist jedoch in Planung. Sie ist aber bereits in Safari enthalten und wird in Chromium 105 veröffentlicht. In allen Demos in diesem Artikel erfahren Sie, falls sie von dem verwendeten Browser nicht unterstützt werden.
Verwendung von :has
Wie sieht das aus? Sehen Sie sich den folgenden HTML-Code mit zwei gleichgeordneten Elementen der Klasse everybody
an. Wie würden Sie das Element auswählen, das ein Nachfolgerelement 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 dies 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 hat ein Nachfolgerelement mit der Klasse a-good-time
.
<target>:has(<condition>) { <styles> }
Sie können aber noch viel weiter gehen, denn :has()
bietet viele Möglichkeiten. Nicht einmal solche, die wahrscheinlich noch nicht entdeckt wurden. Sehen Sie sich einige davon an.
Wählen Sie figure
-Elemente mit einem direkten figcaption
aus.
css
figure:has(> figcaption) { ... }
anchor
-Elemente auswählen, die keinen direkten SVG-Nachfolger haben
css
a:not(:has(> svg)) { ... }
Wählen Sie label
-Elemente aus, die einen direkten input
-Geschwister haben. Seitwärts!
css
label:has(+ input) { … }
Wählen Sie article
-Elemente aus, in denen ein untergeordnetes Element von img
keinen alt
-Text enthält
css
article:has(img:not([alt])) { … }
Wählen Sie die documentElement
aus, bei der ein Status im DOM vorhanden ist
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
Wählen Sie den Layoutcontainer mit einer ungeraden Anzahl von untergeordneten Elementen aus
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
Alle Elemente in einem Raster auswählen, auf die sich nichts bewegt
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
Wählen Sie den Container aus, der ein benutzerdefiniertes Element enthält <todo-list>
css
main:has(todo-list) { ... }
Jedes Solo a
innerhalb eines Absatzes auswählen, das ein direktes gleichgeordnetes hr
-Element hat
css
p:has(+ hr) a:only-child { … }
Wählen Sie eine article
aus, bei der mehrere Bedingungen erfüllt sind
css
article:has(>h1):has(>h2) { … }
Hier findest du einen Mix. Wähle einen article
aus, in dem auf einen Titel ein Untertitel folgt
css
article:has(> h1 + h2) { … }
:root
auswählen, wenn interaktive Status ausgelöst werden
css
:root:has(a:hover) { … }
Wählen Sie den Absatz aus, der auf figure
folgt und kein figcaption
-Zeichen enthält.
css
figure:not(:has(figcaption)) + p { … }
Welche interessanten Anwendungsfälle fallen Ihnen für :has()
ein? Das Interessante dabei ist, dass Sie dazu ermutigt werden, Ihr Denkmodell zu brechen. Da stellt man sich die Frage: „Könnte ich diese Stile auf eine andere Art angehen?“
Beispiele
Sehen wir uns einige Beispiele an.
Karten
Demo zur klassischen Karte ansehen Wir könnten alle Informationen auf unserer Karte anzeigen, zum Beispiel einen Titel, eine Unterüberschrift oder einige 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ühren möchten? Bei diesem Design könnte die Karte in zwei Spalten aufgeteilt werden. Zuvor können Sie eine neue Klasse erstellen, die dieses Verhalten darstellt, z. B. card--with-media
oder card--two-columns
. Diese Klassennamen werden nicht nur schwer zu beschwören, sondern auch schwer zu verwalten und zu merken.
Mit :has()
kannst du erkennen, dass die Karte einige Medien enthält, und die entsprechenden Aktionen ausführen. Es sind keine Namen für Modifikatorklassen 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 nicht dort lassen. Sie könnten kreativ werden. Wie könnte sich eine Karte mit „hervorgehobenen“ Inhalten an ein Layout anpassen? Mit diesem CSS wird eine hervorgehobene Karte über die gesamte Breite des Layouts erstellt und am Anfang eines Rasters platziert.
.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 ist, wenn eine empfohlene 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 dafür bekannt, dass sie knifflig sind. Ein solches Beispiel sind Stileingaben und ihre Labels. Wie signalisieren wir beispielsweise, dass ein Feld gültig ist? Mit :has()
geht das viel einfacher. Wir können in Pseudoklassen wie :valid
und :invalid
einsteigen.
<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);
}
Probieren Sie es in diesem Beispiel aus: Geben Sie gültige und ungültige Werte ein und legen Sie den Fokus nach und nach ab.
Sie können auch :has()
verwenden, um die Fehlermeldung für ein Feld ein- oder auszublenden. Fügen Sie der Feldgruppe „email“ (E-Mail-Adresse) 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 ist die Fehlermeldung ausgeblendet.
.form-group__error {
display: none;
}
Wenn das Feld jedoch zu :invalid
wird und nicht hervorgehoben ist, können Sie die Nachricht anzeigen, ohne zusätzliche Klassennamen zu benötigen.
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
Es gibt keinen Grund, den Nutzern bei der Interaktion mit dem Formular eine geschmackvolle Note zu geben. Betrachten Sie dieses Beispiel: Beobachten Sie, ob Sie einen gültigen Wert für die Mikrointeraktion eingeben. Ein :invalid
-Wert bewirkt, dass die Formulargruppe erschüttert wird. Aber nur, wenn Nutzende keine Bewegungspräferenzen haben.
Inhalt
Wir haben dies in den Codebeispielen angesprochen. Aber wie können Sie :has()
in Ihrem Dokumentfluss verwenden? Sie liefert uns Ideen dazu, wie wir beispielsweise die Typografie für Medien 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 kein figcaption
haben, schweben sie im Inhalt. Wenn figcaption
vorhanden ist, nehmen sie die volle Breite ein und erhalten einen zusätzlichen Rand.
Reaktion auf Zustand
Sie könnten Ihre Stile auf einen bestimmten Zustand in unserem Markup reagieren. Ein Beispiel mit dem „klassischen“ verschiebbare Navigationsleiste. Wenn Sie eine Schaltfläche haben, mit der sich die Navigation umschalten lässt, kann das Attribut aria-expanded
verwendet werden. Die entsprechenden Attribute können mithilfe von JavaScript aktualisiert werden. Wenn aria-expanded
den Wert true
hat, verwenden Sie :has()
, um dies zu ermitteln und die Stile für die gleitende Navigation zu aktualisieren. JavaScript erledigt diese Aufgabe und CSS kann mit diesen Informationen tun, was es will. Das Markup muss nicht umsortiert oder zusätzliche Klassennamen usw. hinzugefügt werden. (Hinweis: Dieses Beispiel ist nicht für die Produktion geeignet.)
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
Kann :has dazu beitragen, Nutzerfehler zu vermeiden?
Was haben alle diese Beispiele gemeinsam? Sie zeigen zwar, wie man :has()
verwendet, aber keiner von ihnen musste Klassennamen ändern. Beide haben neue Inhalte eingefügt und ein Attribut aktualisiert. Dies ist ein großer Vorteil von :has()
, da damit Nutzerfehler reduziert werden können. Mit :has()
kann das CSS die Verantwortung für die Anpassung an Änderungen im DOM übernehmen. Sie müssen Klassennamen in JavaScript nicht ändern, wodurch das Risiko von Entwicklerfehlern verringert wird. Wir alle kennen das, wenn wir einen Klassennamen vertippen, und müssen ihn bei Object
-Suchen beibehalten.
Dies ist ein interessanter Gedanke und führt uns zu saubererem Markup und weniger Code? Weniger JavaScript, da wir weniger JavaScript-Anpassungen vornehmen. Weniger HTML-Code, da Sie keine Klassen wie card card--has-media
mehr benötigen
Um die Ecke denken
Wie bereits erwähnt, ermutigt dich :has()
, das mentale Modell zu brechen. Es ist eine Gelegenheit,
etwas Neues auszuprobieren. Eine Möglichkeit, die Grenzen zu überschreiten, besteht darin, nur mit CSS Spielmechaniken zu entwickeln. Sie könnten beispielsweise eine schrittbasierte 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. Damit könnten Sie ein Formular mit Transformationen durchlaufen. Hinweis: Diese Demo wird am besten in einem separaten Browsertab angezeigt.
Und zum Spaß: Wie wäre es mit dem klassischen Buzz-Wire-Spiel? Die Mechanik lässt sich mit :has()
einfacher erstellen. Wenn du den Mauszeiger darüber bewegst, ist das Spiel beendet. Ja, mithilfe von Kombinatoren (+
und ~
) können wir einige dieser Spielmechaniken erstellen. Mit :has()
erreichen Sie jedoch dieselben Ergebnisse, ohne dass Sie interessante Markup-"Tricks" verwenden müssen. Hinweis: Diese Demo wird am besten in einem separaten Browsertab angezeigt.
Auch wenn Sie diese nicht in Kürze in die Produktion nehmen werden, zeigen sie Möglichkeiten auf, wie Sie die Primitive verwenden können. Beispielsweise kann ein :has()
verkettet werden.
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
Leistung und Einschränkungen
Was kannst du nicht mit :has()
machen? Bei :has()
gelten einige Einschränkungen. Die wichtigsten entstehen durch Leistungssteigerungen.
- Sie können
:has()
nicht:has()
. Sie können aber ein:has()
verketten.css :has(.a:has(.b)) { … }
- Keine Verwendung eines Pseudoelements innerhalb von
:has()
css :has(::after) { … } :has(::first-letter) { … }
- Verwendung von
:has()
in Pseudoen beschränken, die nur zusammengesetzte Selektoren akzeptierencss ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- Verwendung von
:has()
nach Pseudoelement einschränkencss ::part(foo):has(:focus) { … }
- Die Verwendung von
:visited
ist immer falschcss :has(:visited) { … }
Informationen zu tatsächlichen Leistungsmesswerten für :has()
findest du in dieser Störung. Danke an Byungwoo für das Teilen dieser Erkenntnisse und Details zur Implementierung.
Geschafft!
Mach dich bereit für :has()
. Erzählen Sie Ihren Freunden davon und teilen Sie diesen Beitrag mit uns. Er wird die Art und Weise, wie wir CSS angehen, grundlegend verändern.
Alle Demos sind in dieser CodePen-Sammlung verfügbar.