Sejak awal waktu (Dalam istilah CSS), kita telah menggunakan cascade dalam berbagai hal. Gaya kami menyusun "Cascading Style Sheet". Pemilih kami juga akan disusun secara berurutan. Mereka dapat bergerak ke samping. Dalam sebagian besar kasus, ia akan bergerak ke bawah. Namun, tidak pernah ke atas. Selama bertahun-tahun, kami membayangkan "Pemilih induk". Dan sekarang, akhirnya fitur ini hadir. Dalam bentuk pemilih pseudo :has()
.
Pseudo-class CSS :has()
mewakili elemen jika salah satu pemilih yang diteruskan sebagai parameter cocok dengan setidaknya satu elemen.
Namun, ini lebih dari sekadar pemilih "orang tua". Itu cara yang bagus untuk memasarkannya. Cara yang tidak begitu menarik mungkin adalah pemilih "lingkungan bersyarat". Namun, hal itu tidak memiliki arti yang sama. Bagaimana dengan pemilih "keluarga"?
Dukungan Browser
Sebelum kita lanjutkan, ada baiknya untuk menyebutkan dukungan browser. Belum cukup. Namun, hal ini semakin dekat. Belum ada dukungan Firefox, tetapi ada dalam rencana pengembangan. Namun, fitur 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 berurutan dengan class everybody
. Bagaimana cara memilih 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 .everybody
pertama dan menerapkan animation
.
Dalam contoh ini, elemen dengan class everybody
adalah target. Kondisinya adalah memiliki turunan dengan class a-good-time
.
<target>:has(<condition>) { <styles> }
Namun, Anda dapat melakukannya lebih jauh karena :has()
membuka banyak peluang. Bahkan yang mungkin belum ditemukan. Pertimbangkan beberapa hal berikut.
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 saudara input
langsung. Kita ke samping.
css
label:has(+ input) { … }
Pilih article
jika turunan img
tidak memiliki teks alt
css
article:has(img:not([alt])) { … }
Pilih documentElement
jika beberapa status ada 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 diarahkan kursor mouse
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
Pilih penampung yang berisi elemen kustom <todo-list>
css
main:has(todo-list) { ... }
Pilih setiap a
tunggal dalam paragraf yang memiliki elemen hr
saudara langsung
css
p:has(+ hr) a:only-child { … }
Pilih article
jika beberapa kondisi terpenuhi
css
article:has(>h1):has(>h2) { … }
Campur semuanya. Pilih article
dengan judul yang diikuti subtitel
css
article:has(> h1 + h2) { … }
Pilih :root
saat status interaktif dipicu
css
:root:has(a:hover) { … }
Pilih paragraf yang mengikuti figure
yang tidak memiliki figcaption
css
figure:not(:has(figcaption)) + p { … }
Kasus penggunaan menarik apa yang dapat Anda pikirkan untuk :has()
? Hal yang menarik di sini adalah hal ini mendorong Anda untuk mematahkan model mental Anda. Hal ini membuat Anda berpikir, "Apakah saya dapat mendekati gaya ini dengan cara yang berbeda?".
Contoh
Mari kita bahas beberapa contoh cara menggunakannya.
Kartu
Lihat demo kartu klasik. Kita dapat menampilkan informasi apa pun di kartu, misalnya: judul, subtitel, atau beberapa media. Berikut adalah 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 jika Anda ingin memperkenalkan beberapa media? Untuk desain ini, kartu dapat dibagi menjadi dua kolom. Sebelumnya, Anda mungkin membuat class baru untuk mewakili perilaku ini, misalnya card--with-media
atau card--two-columns
. Nama class ini tidak hanya sulit dibuat, tetapi juga sulit dikelola dan diingat.
Dengan :has()
, Anda dapat mendeteksi bahwa kartu memiliki beberapa media dan melakukan tindakan 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>
Dan Anda tidak perlu membiarkannya di sana. Anda dapat berkreasi dengan fitur ini. Bagaimana kartu yang menampilkan konten “unggulan” dapat beradaptasi dalam tata letak? CSS ini akan membuat kartu unggulan memiliki lebar 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 bergoyang 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;
}
Ada begitu banyak kemungkinan.
Formulir
Bagaimana dengan formulir? Gaya rambut ini dikenal sulit diatur. Salah satu contohnya adalah menata gaya input dan labelnya. Bagaimana cara memberi sinyal bahwa kolom valid, misalnya? Dengan :has()
, hal ini menjadi jauh lebih mudah. Kita dapat menghubungkan ke pseudo-class 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);
}
Coba 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 dalamnya.
<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 memerlukan nama class tambahan.
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
Tidak ada alasan Anda tidak dapat menambahkan sentuhan imajinasi yang menarik saat pengguna berinteraksi dengan formulir Anda. Pertimbangkan contoh ini. Tonton saat Anda memasukkan nilai yang valid untuk interaksi mikro. Nilai :invalid
akan menyebabkan grup formulir bergetar. Namun, hanya jika pengguna tidak memiliki preferensi gerakan.
Konten
Kita telah membahasnya dalam contoh kode. Namun, bagaimana Anda dapat menggunakan :has()
dalam alur dokumen? Misalnya, teknik ini akan memunculkan ide tentang cara kita menata gaya tipografi di sekitar media.
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 gambar. Jika tidak memiliki figcaption
, elemen akan mengambang dalam konten. Jika ada figcaption
, figcaption
akan menempati lebar penuh dan mendapatkan margin tambahan.
Merespons Status
Bagaimana jika gaya Anda bereaksi terhadap beberapa status dalam markup kita. Pertimbangkan contoh dengan menu navigasi geser "klasik". Jika Anda memiliki tombol yang mengalihkan pembukaan navigasi, tombol tersebut dapat menggunakan atribut aria-expanded
. JavaScript dapat digunakan untuk memperbarui atribut yang sesuai. Jika aria-expanded
adalah true
, gunakan :has()
untuk mendeteksinya dan memperbarui gaya untuk navigasi geser. JavaScript melakukan tugasnya dan CSS dapat melakukan apa yang diinginkan dengan informasi tersebut. Tidak perlu mengacak markup atau menambahkan nama class tambahan, dll. (Catatan: Ini bukan contoh yang siap produksi).
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
Dapatkah :has membantu menghindari error pengguna?
Apa kesamaan dari semua contoh ini? Selain menunjukkan cara menggunakan :has()
, tidak satu pun dari contoh tersebut yang memerlukan perubahan nama class. Setiap perubahan tersebut menyisipkan konten baru dan memperbarui atribut. Ini adalah manfaat besar dari :has()
, karena dapat membantu mengurangi error pengguna. Dengan :has()
, CSS dapat mengambil tanggung jawab untuk menyesuaikan dengan modifikasi di DOM. Anda tidak perlu mengelola nama class di JavaScript, sehingga mengurangi potensi error developer. Kita semua pernah mengalaminya saat salah ketik nama class dan harus menyimpannya dalam pencarian Object
.
Ini adalah pemikiran yang menarik dan apakah hal ini akan mengarahkan kita ke markup yang lebih bersih dan lebih sedikit kode? Lebih sedikit JavaScript karena kita tidak melakukan banyak penyesuaian JavaScript. HTML yang lebih sedikit karena Anda tidak lagi memerlukan class seperti card card--has-media
, dll.
Berpikir di luar kebiasaan
Seperti yang disebutkan di atas, :has()
mendorong Anda untuk mematahkan model mental. Ini adalah kesempatan untuk mencoba hal-hal yang berbeda. Salah satu cara untuk mencoba dan mendorong batas adalah dengan membuat mekanisme game hanya dengan CSS. Misalnya, Anda dapat membuat mekanisme berbasis langkah dengan formulir dan CSS.
<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;
}
Hal ini membuka kemungkinan yang menarik. Anda dapat menggunakannya untuk menjelajahi formulir dengan transformasi. Perhatikan bahwa demo ini paling baik dilihat di tab browser terpisah.
Dan untuk bersenang-senang, bagaimana dengan game buzz wire klasik? Mekanisme ini lebih mudah dibuat dengan :has()
. Jika kabel diarahkan, game akan berakhir. Ya, kita dapat membuat beberapa mekanisme game ini dengan hal-hal seperti kombinator saudara (+
dan ~
). Namun, :has()
adalah cara untuk mencapai hasil yang sama tanpa harus menggunakan "trik" markup yang menarik. Perhatikan bahwa demo ini paling baik dilihat di tab browser terpisah.
Meskipun Anda tidak akan segera memasukkannya ke dalam produksi, kode ini menyoroti cara Anda dapat menggunakan 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 lanjut, apa yang tidak dapat Anda lakukan dengan :has()
? Ada beberapa batasan terkait :has()
. Masalah utamanya muncul karena penurunan 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) { … }
- Membatasi penggunaan
:has()
di dalam pseudo yang hanya menerima pemilih gabungancss ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- Membatasi 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 tentang penerapan ini.
Selesai!
Bersiaplah untuk :has()
. Beri tahu teman Anda dan bagikan postingan ini. Hal ini akan menjadi game changer untuk cara kita mendekati CSS.
Semua demo tersedia di koleksi CodePen ini.