Sejak dimulai (Dalam istilah CSS), kita telah bekerja dengan jenjang dalam berbagai arti. Gaya kita menyusun "Cascading Style Sheet". Dan pemilih kita juga mengalir. Tombol bisa miring. Umumnya, turun ke bawah. Tapi tidak pernah ke atas. Selama bertahun-tahun, kita mengkhayalkan "Pemilih orang tua". Dan sekarang akhirnya! Dalam bentuk pemilih pseudo :has()
.
Class semu CSS :has()
mewakili sebuah elemen jika salah satu pemilih yang diteruskan sebagai parameter cocok dengan setidaknya satu elemen.
Namun, peran ini lebih dari sekadar "induk" pemilih. Itu cara yang bagus untuk memasarkannya. Cara yang tidak begitu menarik mungkin adalah "lingkungan bersyarat" pemilih. Tapi cincinnya tidak sama persis. Bagaimana dengan "keluarga" pemilih?
Dukungan Browser
Sebelum melangkah lebih jauh, ada baiknya Anda mengetahui dukungan browser. Belum selesai. Tapi, semakin dekat. Belum ada dukungan Firefox. Dukungan ini sudah ada dalam roadmap. Namun, aplikasi ini sudah ada di Safari dan akan dirilis di Chromium 105. Semua demo dalam artikel ini akan memberi tahu Anda jika demo tersebut tidak didukung di browser yang digunakan.
Cara menggunakan :has
Jadi seperti apa bentuknya? Pertimbangkan HTML berikut dengan dua elemen yang setara dengan class everybody
. Bagaimana Anda akan memilih class yang memiliki turunan dengan class a-good-time
?
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
Dengan :has()
, Anda dapat melakukannya dengan CSS berikut.
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
Tindakan ini akan memilih instance pertama .everybody
dan menerapkan animation
.
Dalam contoh ini, elemen dengan class everybody
adalah targetnya. Kondisi ini memiliki turunan dengan class a-good-time
.
<target>:has(<condition>) { <styles> }
Namun, Anda dapat melakukannya lebih jauh karena :has()
membuka banyak peluang. Bahkan mungkin belum ditemukan. Pertimbangkan beberapa di antaranya.
Pilih elemen figure
yang memiliki figcaption
langsung.
css
figure:has(> figcaption) { ... }
Pilih anchor
yang tidak memiliki turunan SVG langsung
css
a:not(:has(> svg)) { ... }
Pilih label
yang memiliki seinduk langsung input
. Pergi ke samping!
css
label:has(+ input) { … }
Pilih article
jika img
turunan tidak memiliki teks alt
css
article:has(img:not([alt])) { … }
Pilih documentElement
yang memiliki beberapa status di DOM
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
Pilih penampung tata letak dengan jumlah turunan ganjil
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
Pilih semua item dalam petak yang tidak ditunjuk dengan kursor
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
Pilih penampung yang berisi elemen khusus <todo-list>
css
main:has(todo-list) { ... }
Pilih setiap solo a
dalam paragraf yang memiliki elemen saudara langsung hr
css
p:has(+ hr) a:only-child { … }
Pilih article
yang beberapa kondisi terpenuhi
css
article:has(>h1):has(>h2) { … }
Padukan. Pilih article
yang judulnya diikuti dengan subtitel
css
article:has(> h1 + h2) { … }
Pilih :root
saat status interaktif dipicu
css
:root:has(a:hover) { … }
Pilih paragraf setelah figure
yang tidak memiliki figcaption
css
figure:not(:has(figcaption)) + p { … }
Apa kasus penggunaan menarik yang dapat Anda pikirkan untuk :has()
? Hal yang menarik di sini adalah kesempatan mendorong Anda untuk mematahkan model mental Anda. Hal ini membuat Anda berpikir "Bisakah saya mendekati gaya ini dengan cara yang berbeda?".
Contoh
Mari kita lihat beberapa contoh bagaimana kita bisa menggunakannya.
Kartu
Ikuti demo kartu klasik. Kita dapat menampilkan informasi apa pun di kartu, misalnya: judul, subjudul, atau media. Berikut kartu dasarnya.
<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>
Apa yang terjadi ketika Anda ingin memperkenalkan beberapa media? Untuk desain ini, kartu dapat dibagi menjadi dua kolom. Sebelumnya, Anda dapat membuat class baru untuk mewakili perilaku ini, misalnya card--with-media
atau card--two-columns
. Nama class ini tidak hanya sulit untuk dibuat, tetapi juga menjadi sulit dikelola dan diingat.
Dengan :has()
, Anda dapat mendeteksi bahwa kartu memiliki beberapa media dan melakukan hal yang sesuai. Tidak perlu nama class pengubah.
<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>
Anda juga tidak perlu meninggalkannya. Anda bisa menggunakannya untuk berpikir kreatif. Bagaimana kartu yang menampilkan konten “unggulan” dapat beradaptasi dalam tata letak? CSS ini akan membuat kartu unggulan selebar penuh tata letak dan menempatkannya di awal petak.
.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);
}
Bagaimana jika kartu unggulan dengan banner bergerak untuk menarik perhatian?
<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;
}
Begitu banyak kemungkinan.
Formulir
Bagaimana dengan formulir? Mereka terkenal sulit untuk ditata gayanya. Salah satu contoh hal ini adalah penataan gaya input dan labelnya. Misalnya, bagaimana cara kami menunjukkan bahwa suatu kolom valid? Dengan :has()
, proses ini menjadi jauh lebih mudah. Kita dapat menghubungkan ke class semu formulir yang relevan, misalnya :valid
dan :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);
}
Cobalah dalam contoh ini: Coba masukkan nilai yang valid dan tidak valid, lalu aktifkan dan nonaktifkan fokus.
Anda juga dapat menggunakan :has()
untuk menampilkan dan menyembunyikan pesan error untuk kolom. Ambil grup kolom “email” dan tambahkan pesan error ke grup tersebut.
<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>
Secara default, Anda menyembunyikan pesan error.
.form-group__error {
display: none;
}
Namun, saat kolom menjadi :invalid
dan tidak difokuskan, Anda dapat menampilkan pesan tanpa perlu nama class tambahan.
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
Tidak ada alasan untuk menambahkan sedikit imajinasi lucu saat pengguna berinteraksi dengan formulir Anda. Pikirkan contoh ini. Tonton saat Anda memasukkan nilai yang valid untuk interaksi mikro. Nilai :invalid
akan menyebabkan grup formulir bergoyang. Namun, hanya jika pengguna tidak memiliki preferensi gerakan.
Konten
Kita telah membahasnya dalam contoh kode. Namun, bagaimana Anda dapat menggunakan :has()
dalam alur dokumen Anda? Hal tersebut memunculkan ide tentang bagaimana kita bisa menata gaya tipografi di sekitar media, misalnya.
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%;
}
Contoh ini berisi angka. Jika tidak memiliki figcaption
, elemen ini akan mengambang di dalam konten. Jika ada, figcaption
akan menempati lebar penuh dan mendapatkan margin tambahan.
Bereaksi terhadap Status
Bagaimana kalau membuat gaya Anda menjadi reaktif terhadap beberapa status di markup kami. Pertimbangkan contoh dengan "klasik" bilah navigasi geser. Jika Anda memiliki tombol yang mengaktifkan/menonaktifkan navigasi, tombol tersebut dapat menggunakan atribut aria-expanded
. JavaScript dapat digunakan untuk memperbarui atribut yang sesuai. Bila aria-expanded
adalah true
, gunakan :has()
untuk mendeteksi hal ini dan perbarui gaya untuk navigasi geser. JavaScript melakukan perannya dan CSS dapat melakukan apa yang diinginkannya dengan informasi itu. Tidak perlu mengacak markup atau menambahkan nama class tambahan, dll. (Catatan: Ini bukan contoh dalam mode produksi).
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
Dapatkah :has membantu menghindari kesalahan pengguna?
Apa kesamaan dari semua contoh-contoh ini? Selain menunjukkan cara menggunakan :has()
, keduanya tidak memerlukan modifikasi nama class. Mereka masing-masing menyisipkan konten baru dan memperbarui atribut. Hal ini adalah manfaat besar :has()
, karena dapat membantu mengurangi error pengguna. Dengan :has()
, CSS dapat bertanggung jawab untuk menyesuaikan modifikasi di DOM. Anda tidak perlu membolak-balik nama class di JavaScript, sehingga mengurangi potensi error developer. Kita semua pernah berada di sana saat salah mengetik nama class dan harus menyimpannya dalam pencarian Object
.
Ini merupakan pemikiran yang menarik dan apakah hal ini membawa kita menuju markup yang lebih bersih dan lebih sedikit kode? Lebih sedikit JavaScript karena kami tidak melakukan banyak penyesuaian JavaScript. Lebih sedikit HTML karena Anda tidak lagi memerlukan class seperti card card--has-media
, dll.
Berpikir kreatif
Seperti disebutkan di atas, :has()
mendorong Anda untuk merusak model mental. Ini adalah kesempatan untuk mencoba berbagai hal. Salah satu cara untuk mencoba dan menembus batas adalah dengan membuat mekanisme game hanya dengan CSS. Anda dapat membuat mekanika berbasis langkah dengan formulir dan CSS, misalnya.
<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;
}
Dan hal itu membuka kemungkinan yang menarik. Anda bisa menggunakannya untuk menjelajahi formulir dengan transformasi. Perhatikan bahwa demo ini paling optimal dilihat di tab browser terpisah.
Dan untuk bersenang-senang, bagaimana dengan
permainan dengkuran klasik? Mekanik lebih mudah dibuat dengan :has()
. Jika kabel diarahkan ke atas, game selesai. Ya, kita dapat membuat beberapa mekanika game ini dengan fitur seperti kombinator seinduk (+
dan ~
). Namun, :has()
adalah cara untuk mencapai hasil yang sama tanpa harus menggunakan "trik" markup yang menarik. Perhatikan bahwa demo ini paling optimal dilihat di tab browser terpisah.
Meskipun Anda tidak akan merilisnya ke tahap produksi dalam waktu dekat, hal ini menyoroti cara menggunakan versi primitif. Misalnya, dapat membuat rantai :has()
.
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
Performa dan batasan
Sebelum kita pergi, apa yang tidak dapat Anda lakukan dengan :has()
? Ada beberapa batasan dengan :has()
. Penyebab utama muncul karena hit performa.
- Anda tidak dapat
:has()
:has()
. Namun, Anda dapat membuat rantai:has()
.css :has(.a:has(.b)) { … }
- Tidak ada penggunaan elemen pseudo dalam
:has()
css :has(::after) { … } :has(::first-letter) { … }
- Batasi penggunaan
:has()
di dalam pseudo hanya menerima pemilih gabungancss ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- Batasi penggunaan
:has()
setelah elemen pseudocss ::part(foo):has(:focus) { … }
- Penggunaan
:visited
akan selalu salahcss :has(:visited) { … }
Untuk metrik performa sebenarnya yang terkait dengan :has()
, lihat Glitch ini. Terima kasih kepada Byungwoo yang telah membagikan insight dan detail terkait implementasi tersebut.
Selesai!
Bersiaplah untuk :has()
. Beri tahu teman Anda tentang hal itu dan bagikan postingan ini, akan membawa perubahan besar dalam pendekatan kami terhadap CSS.
Semua demo tersedia di Pengumpulan CodePen ini.