CSS MODERN-CSS FRONTEND WEB-DEVELOPMENT UI-UX STYLING CSS-SELECTOR JAVASCRIPT-REDUCTION INTERACTIVITY BEST-PRACTICES DEVELOPER-EXPERIENCE

CSS :has(): Jurus Rahasia Styling Dinamis dan Seleksi Parent di Web Modern

⏱️ 9 menit baca
👨‍💻

CSS :has(): Jurus Rahasia Styling Dinamis dan Seleksi Parent di Web Modern

1. Pendahuluan

Pernahkah Anda merasa frustrasi karena tidak bisa menyeleksi elemen “parent” di CSS? Atau harus mengandalkan JavaScript hanya untuk mengubah style suatu elemen berdasarkan keberadaan atau kondisi elemen child-nya? Jika ya, Anda tidak sendirian. Ini adalah salah satu keterbatasan klasik CSS yang telah lama menjadi keluhan para developer. Namun, era tersebut kini telah berakhir!

Hadirnya :has() pseudo-class di CSS adalah revolusi yang mengubah cara kita berpikir tentang styling web. :has() bukan sekadar selektor biasa; ia adalah “parent selector” yang kita impikan, dan lebih dari itu. Dengan :has(), Anda bisa menyeleksi elemen berdasarkan apa yang ada di dalamnya, memungkinkan Anda menciptakan styling yang jauh lebih dinamis, cerdas, dan reaktif, tanpa perlu menyentuh sebaris pun JavaScript.

Artikel ini akan membawa Anda menyelami kekuatan :has(), dari konsep dasar hingga contoh praktis yang akan membuat workflow styling Anda jauh lebih efisien dan menyenangkan. Bersiaplah untuk mengucapkan selamat tinggal pada banyak boilerplate JavaScript dan menyambut kekuatan baru di CSS Anda!

2. Memahami CSS :has(): Sang “Parent Selector” dan Lebih Banyak Lagi

Secara sederhana, A:has(B) berarti “pilih elemen A jika elemen A memiliki (has) elemen B di dalamnya.” Konsep ini sangat fundamental namun sangat powerful karena membalikkan logika seleksi tradisional CSS.

Secara historis, CSS hanya bisa menyeleksi ke bawah (descendant selectors) atau ke samping (sibling selectors). Kita tidak bisa menyeleksi elemen induk (parent) berdasarkan kondisi elemen anak (child). Misalnya, jika kita ingin memberikan border merah pada <div> hanya jika di dalamnya ada <p> dengan class error, sebelumnya kita harus melakukannya dengan JavaScript. Kini, dengan :has(), itu menjadi mungkin.

/* Pilih div yang memiliki child dengan class 'error' */
div:has(.error) {
  border: 2px solid red;
  background-color: #ffe6e6;
}

Tapi, jangan batasi pikiran Anda hanya pada “parent selector.” :has() jauh lebih fleksibel. Anda bisa menggunakan kombinasi selektor CSS apa pun di dalamnya, dan bahkan menargetkan elemen lain relatif terhadap elemen yang ditemukan di dalam :has(). Ini membuka gerbang ke skenario styling yang tak terbatas!

🎯 Analogi Sederhana: Bayangkan Anda sedang mencari sebuah rumah di sebuah kompleks perumahan.

3. Contoh Dasar: Styling Parent Berdasarkan Child

Mari kita lihat beberapa contoh konkret bagaimana :has() dapat menyederhanakan styling sehari-hari Anda.

3.1. Kartu dengan atau Tanpa Gambar

Bayangkan Anda memiliki komponen kartu (.card) yang terkadang memiliki gambar (.card-image) dan terkadang tidak. Anda ingin kartu yang memiliki gambar memiliki padding yang berbeda atau bayangan yang lebih menonjol.

Cara Lama (dengan JS atau class tambahan):

<!-- Perlu class tambahan atau JS untuk menambah/menghapus class -->
<div class="card card--has-image">
  <img src="image.jpg" alt="Card Image" class="card-image">
  <div class="card-content">...</div>
</div>

<div class="card">
  <div class="card-content">...</div>
</div>
.card {
  padding: 1rem;
}

.card--has-image {
  padding: 0.5rem; /* Lebih sedikit padding karena gambar */
  box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

Cara Baru (dengan :has()):

<!-- Tidak perlu class tambahan, CSS yang menentukan! -->
<div class="card">
  <img src="image.jpg" alt="Card Image" class="card-image">
  <div class="card-content">...</div>
</div>

<div class="card">
  <div class="card-content">...</div>
</div>
.card {
  padding: 1rem;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

/* Jika .card memiliki .card-image di dalamnya */
.card:has(.card-image) {
  padding: 0.5rem;
  box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

💡 Manfaat: Kode HTML lebih bersih, tidak perlu logika JS untuk mengelola class styling, dan styling menjadi lebih deklaratif.

3.2. Form Input yang Valid/Invalid

Anda ingin label atau input field berubah warna jika input tersebut dalam kondisi invalid.

Cara Lama (dengan JS atau selektor :invalid pada input): Selektor :invalid hanya bekerja pada input, bukan label parent-nya. Untuk label, biasanya butuh JS atau struktur HTML yang lebih rumit.

<div class="form-group">
  <label for="email">Email</label>
  <input type="email" id="email" required class="is-invalid"> <!-- class ditambahkan JS -->
  <span class="error-message">Email tidak valid.</span>
</div>

Cara Baru (dengan :has()):

<div class="form-group">
  <label for="email">Email</label>
  <input type="email" id="email" required>
  <span class="error-message">Email tidak valid.</span>
</div>
.form-group label {
  color: #333;
}

/* Jika .form-group memiliki input yang invalid */
.form-group:has(input:invalid) label {
  color: red;
}

.form-group:has(input:invalid) .error-message {
  display: block;
  color: red;
  font-size: 0.8em;
}

.error-message {
  display: none; /* Sembunyikan secara default */
}

📌 Catatan: Di sini, :has() digabungkan dengan pseudo-class :invalid yang sudah ada di CSS, menunjukkan bagaimana ia dapat memperluas kapabilitas selektor yang sudah ada.

4. Lebih dari Sekadar Parent Selector: Membangun UI yang Lebih Cerdas

Kekuatan :has() tidak berhenti pada seleksi parent. Ia bisa menyeleksi elemen apa pun yang memenuhi kondisi di dalamnya, termasuk elemen sibling atau bahkan elemen yang tidak langsung menjadi child.

4.1. Styling Sibling Berdasarkan Kondisi Child

Anda memiliki daftar item, dan ingin memberikan style khusus pada item sebelum item yang sedang hover atau aktif.

<ul class="product-list">
  <li class="product-item">Item 1</li>
  <li class="product-item">Item 2</li>
  <li class="product-item">Item 3</li>
</ul>
.product-item {
  margin-bottom: 10px;
  background-color: lightblue;
}

/* Jika .product-item di-hover, maka .product-item sebelumnya akan mendapatkan style ini */
.product-item:has(+ .product-item:hover) {
  background-color: yellow;
  border-bottom: 2px solid orange;
}

Di sini, + .product-item:hover berarti “sibling yang langsung mengikuti dan sedang di-hover”. Jadi, :has(+ .product-item:hover) akan menyeleksi .product-item yang memiliki sibling .product-item yang sedang di-hover setelahnya. Ini adalah contoh bagaimana :has() memungkinkan kita menyeleksi elemen berdasarkan kondisi elemen setelahnya, sesuatu yang sebelumnya mustahil di CSS.

4.2. Layout Dinamis Berdasarkan Jumlah Item

Anda memiliki grid atau flexbox dan ingin mengubah layout jika ada jumlah item tertentu.

<div class="gallery">
  <img src="img1.jpg">
  <img src="img2.jpg">
  <img src="img3.jpg">
  <!-- <img src="img4.jpg"> -->
</div>
.gallery {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(3, 1fr); /* Default 3 kolom */
}

/* Jika gallery hanya memiliki 1 gambar */
.gallery:has(img:only-child) {
  grid-template-columns: 1fr; /* Jadi 1 kolom */
  place-items: center;
}

/* Jika gallery memiliki 2 gambar */
.gallery:has(img:nth-child(2):last-child) {
  grid-template-columns: repeat(2, 1fr); /* Jadi 2 kolom */
}

⚠️ Perhatian: Selektor ini bisa menjadi kompleks. Pastikan untuk menguji performa jika digunakan pada daftar dengan banyak item.

5. Mengurangi Ketergantungan pada JavaScript

Salah satu keuntungan terbesar :has() adalah kemampuannya untuk mengambil alih banyak tugas styling yang sebelumnya harus ditangani oleh JavaScript. Ini berarti bundle JS Anda bisa lebih kecil, performa lebih cepat, dan kode lebih mudah di-maintain.

5.1. Toggle Class Sederhana

Sebelumnya, untuk toggle sebuah menu atau modal, kita sering menambahkan/menghapus class dengan JS.

Cara Lama (dengan JS):

<button id="toggleButton">Toggle Menu</button>
<div id="menu" class="menu">Menu Content</div>
document.getElementById('toggleButton').addEventListener('click', () => {
  document.getElementById('menu').classList.toggle('is-open');
});
.menu { display: none; }
.menu.is-open { display: block; }

Cara Baru (dengan :has() dan checkbox tersembunyi): Kita bisa memanfaatkan state checkbox yang tersembunyi.

<input type="checkbox" id="menu-toggle" hidden>
<label for="menu-toggle" class="toggle-button">Toggle Menu</label>

<div class="menu">
  Menu Content
</div>
.menu {
  display: none;
}

/* Jika checkbox #menu-toggle dicentang, dan ia memiliki sibling .menu setelahnya */
#menu-toggle:checked ~ .menu {
  display: block;
}

/* Styling untuk label button */
.toggle-button {
  cursor: pointer;
  padding: 0.5rem 1rem;
  background-color: #007bff;
  color: white;
  border-radius: 5px;
}

Ini adalah pola yang sangat powerful untuk mengelola state UI sederhana murni dengan CSS dan HTML, tanpa JS sama sekali!

6. Kombinasi dengan Fitur CSS Modern Lain

:has() bersinar terang ketika dikombinasikan dengan fitur-fitur CSS modern lainnya, menciptakan kemungkinan styling yang belum pernah ada sebelumnya.

6.1. Dengan CSS Custom Properties (Variabel CSS)

Anda bisa mengubah nilai custom property pada parent berdasarkan kondisi child, lalu menggunakan custom property tersebut di child atau sibling lainnya.

<div class="theme-wrapper">
  <div class="card">
    <button class="primary-action">Action</button>
  </div>
  <div class="card">
    <button class="secondary-action">Action</button>
  </div>
</div>
.theme-wrapper:has(.card:hover) {
  --hover-color: #ff00ff; /* Ubah variabel jika ada kartu di-hover */
}

.card {
  border: 1px solid var(--hover-color, lightgray); /* Gunakan variabel */
}

/* Atau bahkan mengubah sibling lain */
.card:has(.primary-action):hover ~ .card {
  opacity: 0.5; /* Sibling card lain jadi transparan */
}

6.2. Dengan Container Queries

Kombinasi ini sangat kuat untuk komponen yang responsif secara intrinsik. Anda bisa mengubah gaya container berdasarkan keberadaan child tertentu, dan kemudian mengubah gaya child lain di dalam container tersebut berdasarkan ukuran container.

<div class="product-container">
  <div class="product-card">
    <img src="product.jpg" alt="Product Image">
    <h3>Nama Produk</h3>
    <p class="discount">Diskon 20%!</p>
    <button>Beli Sekarang</button>
  </div>
  <div class="product-card">
    <h3>Nama Produk Tanpa Gambar</h3>
    <p>Harga normal</p>
    <button>Detail</button>
  </div>
</div>
.product-container {
  container-type: inline-size;
}

/* Jika product-card memiliki diskon, ubah layout-nya */
.product-card:has(.discount) {
  border: 2px dashed red;
}

/* Di dalam product-container, jika lebarnya kecil DAN ada diskon */
@container (max-width: 400px) and (.product-card:has(.discount)) {
  .product-card .discount {
    font-size: 1.2rem;
    color: purple;
  }
}

Ini adalah masa depan CSS yang sangat adaptif dan modular!

7. Pertimbangan Performa dan Kompatibilitas Browser

7.1. Kompatibilitas Browser

✅ Dukungan browser untuk :has() sudah sangat baik di browser modern (Chrome 105+, Firefox 121+, Safari 16.4+). Mayoritas pengguna sudah bisa menikmati fitur ini. ❌ Untuk browser lama atau yang belum mendukung, Anda bisa menggunakan @supports rule untuk memberikan fallback styling atau mengandalkan JavaScript jika memang esensial.

/* Fallback untuk browser yang tidak mendukung :has() */
@supports not selector(:has(div)) {
  .card-with-image { /* class ini harus ditambahkan via JS */
    padding: 0.5rem;
  }
}

7.2. Performa

Secara umum, tim browser telah mengoptimalkan :has() agar berkinerja baik. Namun, seperti selektor CSS lainnya, selektor yang terlalu kompleks atau terlalu umum (misalnya *:has(*)) dapat memengaruhi performa rendering, terutama pada DOM yang sangat besar.

💡 Tips:

Kesimpulan

CSS :has() pseudo-class adalah tambahan yang game-changing untuk bahasa styling web. Ia membuka dimensi baru dalam styling dinamis dan responsif yang sebelumnya hanya bisa diatasi dengan JavaScript atau struktur HTML yang rumit. Dengan :has(), Anda dapat:

Meskipun dukungan browser sudah matang, selalu penting untuk memahami cara kerjanya dan mempertimbangkan performa serta fallback untuk memastikan pengalaman terbaik bagi semua pengguna. Mulailah bereksperimen dengan :has() dalam proyek Anda dan rasakan sendiri bagaimana ia akan merevolusi cara Anda menulis CSS!

🔗 Baca Juga