Saya merapikan style checkbox di sebuah situs yang saya bangun: memindahkan aturan styled-checkbox dari file CSS per-halaman ke stylesheet komponen global, supaya bisa dipakai konsisten di mana saja. Idenya bagus — kecuali di halaman /checkout, di mana state custom checkbox saya (kotak terisi oranye dengan centang putih) tetap tidak pernah muncul. Yang tampil di sana adalah checkbox bawaan browser, kotak abu-abu standar.
Anehnya, di halaman lain styling-nya sempurna. Hanya di /checkout yang gagal — persis halaman yang paling tidak ingin saya rusak.
Komponen global-nya seperti ini:
.checkbox input[type="checkbox"] {
position: absolute;
opacity: 0; /* sembunyikan input bawaan */
}
.checkbox .box {
width: 18px; height: 18px;
border: 1px solid #ccc;
}
.checkbox input:checked + .box {
background: #f97316; /* oranye */
/* centang putih dirender di sini */
}Aturan opacity: 0 itu seharusnya menyembunyikan input bawaan supaya .box custom bisa menggantikannya. Di kebanyakan halaman, itu berhasil. Di /checkout, input bawaan tetap terlihat — yang artinya opacity: 0 saya kalah.
Akar masalahnya: selector lama yang lebih spesifik masih menang
Saya buka DevTools, klik input bawaan, dan lihat panel Computed → Styles. Di situ jelas: aturan opacity: 0 saya dicoret (strike-through), dan satu aturan lebih lama menang di atasnya:
.checkout-page .checkbox input[type="checkbox"] {
opacity: 1; /* ← yang menang */
position: static;
}Aturan ini sisa dari lokasi lama style checkbox — dulu ada di file per-halaman checkout, sengaja memaksa input bawaan tetap terlihat. Saya memindahkan style yang baru ke stylesheet global, tapi lupa menghapus override page-scoped ini.
Dan inilah inti masalahnya — specificity. Hitung kedua selector:
.checkbox input[type="checkbox"] → (0,2,1) = 1 class + 1 attr + 1 elemen
.checkout-page .checkbox input[type="checkbox"] → (0,3,1) = 2 class + 1 attr + 1 elemen
Selector lama membawa ancestor .checkout-page ekstra, jadi specificity-nya lebih tinggi. Di cascade, specificity tertinggi menang — tidak peduli stylesheet mana yang dimuat belakangan. Jadi opacity: 1 lama mengalahkan opacity: 0 global saya, input bawaan tidak pernah disembunyikan, dan state custom tidak punya ruang untuk dirender.
Yang menipu: aturan global saya dimuat lebih akhir di dokumen. Insting umum adalah "yang terakhir menang", tapi urutan sumber hanya jadi penentu ketika specificity sama. Di sini specificity tidak sama — jadi .checkout-page menang meskipun didefinisikan lebih dulu.
Cara melacaknya di DevTools
Ini polanya, supaya Anda bisa mengenalinya dalam hitungan detik bukan jam:
- Inspect elemen yang salah (di sini, input bawaan yang muncul).
- Buka tab Computed, cari properti yang bermasalah (
opacity), klik panahnya untuk expand. - DevTools mengurut semua aturan yang berkontribusi dari pemenang ke pecundang. Pemenang di atas; yang kalah dicoret (strike-through).
- Lihat aturan teratas yang tidak dicoret — itu yang sedang menang. Di sini:
.checkout-page .checkbox input[type="checkbox"]. - Selector dengan ancestor ekstra (
.checkout-page) hampir selalu jadi tersangka pada bug "kenapa cuma di halaman ini".
Begitu Anda melihat aturan global Anda dicoret sementara aturan ber-ancestor lebih panjang yang menang, diagnosisnya sudah selesai: ini perang specificity, bukan masalah loading order.
Perbaikannya: hapus override page-scoped yang lama
Tidak perlu !important, tidak perlu menaikkan specificity aturan global (yang justru menyebarkan masalah). Perbaikan yang benar adalah menghapus selector page-scoped yang dulu bersaing dengan aturan di lokasi lamanya:
/* HAPUS dari file checkout — peninggalan dari lokasi lama */
.checkout-page .checkbox input[type="checkbox"] {
opacity: 1;
position: static;
}Begitu override ini hilang, aturan .checkbox global akhirnya menang di /checkout seperti di halaman lain. Input bawaan tersembunyi, dan kotak oranye dengan centang putih akhirnya muncul.
Pelajaran
Ketika Anda mempromosikan CSS sebuah komponen dari file per-halaman ke stylesheet "global", jangan cuma memindahkan aturan baru — buru juga selector page-scoped lama yang dulu bersaing dengannya. Selector itu sering membawa ancestor ekstra (.checkout-page, .single-product, dan sebagainya) yang memberinya specificity lebih tinggi, sehingga mereka terus menang persis di halaman yang paling Anda pedulikan. Loading order tidak akan menyelamatkan Anda — specificity yang menentukan. Buka panel Computed, lihat aturan mana yang menang dan mana yang dicoret, lalu hapus override yang sudah tidak berguna itu, bukan menumpuk !important di atasnya.
