Tiga pseudo-class fungsional ini sering dijelaskan berbarengan, tetapi masing-masing menjawab persoalan yang berbeda: :is() merapatkan daftar selector, :where() menyediakan gaya yang dapat ditimpa tanpa perlawanan, dan :has() memungkinkan sebuah elemen dipilih berdasarkan isinya. Teori specificity :is() dan :where() telah kita bahas pada Chapter Specificity
; perilaku forgiving daftar selector :is() juga telah muncul pada Chapter Selector Lists
. Chapter ini menyusun keduanya menjadi pola pemakaian praktis dan menambahkan :has() — Baseline sejak 2023 — yang membuka pemilihan induk tanpa JavaScript. Ketiga polanya kita uji dalam satu demo yang dapat diverifikasi.
:is() untuk Pengelompokan yang Ringkas #
:is()
mengambil daftar selector dan cocok bila salah satu anggotanya cocok. Pola paling umum adalah menggantikan selector list panjang yang berulang:
/* Selector berulang: setiap jalur ditulis penuh */
.papan h2, .papan h3, .modal h2, .modal h3 { color: rgb(30, 64, 175); }
/* :is() merapatkan jalur induknya */
:is(.papan, .modal) :is(h2, h3) { color: rgb(30, 64, 175); }Kedua aturan itu setara dalam hasil, tetapi versi :is() menyatakan maksudnya lebih jelas: “judul tingkat dua atau tiga, di dalam papan atau modal”. Ingat pula dua sifat yang pernah kita bahas: list di dalam :is() bersifat forgiving — anggota tidak valid diabaikan tanpa menggagalkan aturan — dan specificity :is() mengikuti anggota paling spesifik.
Sifat forgiving itu juga membuat :is() menjadi tempat yang tepat untuk selector yang menunjuk fitur belum merata dukungannya. Anggota yang belum dikenal browser hari ini diabaikan tanpa merusak anggota lain; setelah browser menambahkan dukungan, anggota tersebut mulai bekerja tanpa perubahan stylesheet. Pola ini menggantikan kebiasaan lama menulis aturan terpisah demi menjaga satu selector eksperimental tidak menjatuhkan yang lain.
:where() sebagai Gaya yang Pasrah #
:where()
bekerja persis seperti :is(), dengan satu perbedaan: specificity-nya selalu nol. Sifat ini membuatnya ideal untuk gaya default yang dirancang untuk ditimpa tanpa perlawanan. Demo chapter ini membandingkannya langsung:
/* Aturan default dengan class: (0, 1, 1) */
.kartu h3 { color: rgb(21, 128, 61); }
/* Pengelompokan: specificity = anggota tertinggi (0, 0, 1) */
:is(h2, h3) { color: rgb(30, 64, 175); }
/* Default pasrah: specificity nol → (0, 0, 1) */
:where(.kartu) h3 { color: rgb(220, 38, 38); }Meskipun aturan :where() ditulis paling belakang, aturan itu kalah oleh .kartu h3 — specificity (0, 1, 1) mengalahkan (0, 0, 1) apa pun urutan penulisan. Inilah pola “default pasrah”: kita menyediakan gaya awal tanpa memaksa komponen lain menaikkan specificity untuk menimpanya. Tanpa :where(), gaya default biasanya ditulis dengan specificity tinggi dan penimpanya pun harus semakin tinggi.
:has() untuk Memilih Berdasarkan Isi #
:has()
mencocokkan elemen yang memuat keturunan atau saudara sesuai selector argumennya, sehingga induk akhirnya dapat dipilih. Pola lazimnya menata panel berdasarkan kontennya:
.panel {
border: 2px solid rgb(203, 213, 225);
border-radius: 12px;
}
/* Panel yang memuat badge mendapat garis biru */
.panel:has(.badge) { border-color: rgb(2, 132, 199); }
/* Panel yang memuat gambar mendapat latar biru muda */
.panel:has(img) { background-color: rgb(240, 249, 255); }Aturan pertama membaca: “pilih .panel yang di dalamnya terdapat elemen .badge”. Tanpa :has(), kebutuhan semacam ini ditangani JavaScript yang menambahkan class pada induk saat konten berubah — dengan :has(), browser mengeceknya sendiri dan hasilnya mengikuti perubahan DOM secara langsung. Sifat forgiving yang sama dengan :is() juga berlaku di dalam argumen :has().
Argumen :has() tidak terbatas pada selector keturunan. Combinator di dalamnya membuka pola berbasis saudara, misalnya memilih label yang diikuti input tidak valid, atau memilih wadah yang setelahnya terdapat elemen tertentu. Untuk form, kombinasi :has() dengan pseudo-class validasi yang telah kita bahas pada Chapter Required dan Invalid
menghasilkan penanda galat pada label atau wadah — bukan hanya pada input itu sendiri.
Demo: Tiga Pola dalam Satu Halaman #
<h2 data-expected='{"color":"rgb(30, 64, 175)"}'>Ruang Kerja</h2>
<div class="kartu">
<h3 data-expected='{"color":"rgb(21, 128, 61)"}'>Tugas Pekan Ini</h3>
</div>
<div class="panel" data-expected='{"border-color":"rgb(2, 132, 199)"}'>
<span class="badge">Baru</span>
<p>Panel dengan badge.</p>
</div>
<div class="panel" data-expected='{"border-color":"rgb(203, 213, 225)"}'>
<p>Panel polos tanpa badge.</p>
</div>Hasil di Browser:
Judul h2 di luar kartu tampil biru tua (color: rgb(30, 64, 175))
— hanya aturan :is(h2, h3) yang cocok.
Judul h3 di dalam kartu tampil hijau (color: rgb(21, 128, 61)) —
aturan .kartu h3 menang atas :is() maupun :where() berkat specificity.
Panel dengan badge bergaris biru (border-color: rgb(2, 132, 199))
lewat .panel:has(.badge); panel polos tetap bergaris abu-abu
(border-color: rgb(203, 213, 225)).
Panel ketiga yang memuat gambar berlatar biru muda
(background-color: rgb(240, 249, 255)) lewat .panel:has(img).Demo lengkap membawa atribut data-expected pada judul dan ketiga panel, memverifikasi pemenang warna tiap pola secara otomatis — termasuk bukti bahwa :where() pasrah dan :has() selektif terhadap isi panelnya.