Banyak konflik CSS tidak selesai di source order karena dua selector yang bersaing jelas-jelas tidak setara — satu memakai class, satu lagi memakai id. Untuk situasi ini browser memakai kriteria ketiga: specificity, yaitu bobot numerik yang dihitung dari susunan selector. Dengan formula sederhana (a, b, c), kita bisa membandingkan dua aturan mana pun sebelum menjalankannya, sehingga hasilnya tidak perlu ditebak di browser.
Formula (a, b, c) #
Specificity ditulis sebagai tiga angka yang dihitung per selector, bukan per rule. Ketiga angkanya dihitung dari komponen yang berbeda:
| Komponen | Dihitung dari | Contoh yang menambah angka |
|---|---|---|
| a | jumlah selector id | #header |
| b | jumlah class, attribute selector, dan pseudo-class | .tombol, [type="checkbox"], :hover |
| c | jumlah element type dan pseudo-element | ul, a, ::before |
Mari kita latih pada selector yang sering muncul:
| Selector | a | b | c | Catatan |
|---|---|---|---|---|
p | 0 | 0 | 1 | satu element type |
.nav a | 0 | 1 | 1 | satu class, satu element |
ul li.active | 0 | 1 | 2 | satu class, dua element |
#header .tombol | 1 | 1 | 0 | satu id, satu class |
Perhatikan ul li.active: class memberi angka pada kolom b, sementara ul dan li mengisi kolom c. Tiga angka ini ditulis berurutan sebagai (0, 1, 2) dan menjadi identitas bobot selector tersebut.
Membandingkan Dua Selector #
Perbandingan dilakukan dari kiri ke kanan. Bandingkan angka a terlebih dahulu; bila seri, lanjut ke b; bila masih seri, lanjut ke c; bila ketiganya seri, keputusan jatuh ke source order. Tidak ada penghitungan total — angka (1, 0, 0) selalu mengalahkan (0, 12, 5) karena kolom a lebih dulu dinilai. Inilah sebabnya satu id mampu mengalahkan belasan class sekaligus.
Sebagai latihan, bandingkan #header .tombol (1, 1, 0) dengan .nav ul li a.aktif (0, 2, 3). Selector kedua memakai lebih banyak komponen, tetapi kolom a-nya nol, sehingga selector pertama menang tanpa perlu melihat kolom lain:
#header .tombol { /* (1, 1, 0) — kolom a langsung memutuskan */
color: #14503a;
}
.nav ul li a.aktif { /* (0, 2, 3) — banyak komponen, tetapi tanpa id */
color: #475569;
}Tautan di atas ditarget kedua rule sekaligus; menambah tiga komponen lagi ke selector kedua pun tidak mengubah hasil, karena perbandingan berhenti di kolom a.
Beberapa komponen selector punya perlakuan khusus dalam perhitungan:
| Selector | Bobot | Catatan |
|---|---|---|
* | 0, 0, 0 | universal selector tidak menambah angka apa pun |
.menu :not(.aktif) | 0, 2, 0 | :not() sendiri bernilai nol; argumen di dalamnya yang dihitung |
:is(#promo, .kartu) p | 1, 0, 1 | :is() dan :has() mengambil bobot tertinggi dari argumennya |
Pada baris terakhir, :is() menampung dua kandidat: #promo (1, 0, 0) dan .kartu (0, 1, 0). Browser memakai yang tertinggi, yaitu #promo, sehingga bobot keseluruhan menjadi (1, 0, 1). Menambahkan pilihan lemah ke dalam :is() tidak menurunkan bobotnya.
Dua tombol berikut memakai class yang sama, tetapi tombol kedua berada di dalam elemen ber-id undangan sehingga selector #undangan .tombol ikut menargetkannya:
Kedua deklarasi bersaing pada property yang sama, tetapi source order tidak sempat bekerja karena specificity sudah memisahkan keduanya.
Inline Style Ada di Tingkatan Sendiri #
Deklarasi yang ditulis langsung pada atribut style elemen berada di atas seluruh selector:
<p style="color: #b45309;">Status pembayaran tertunda.</p>Pada demo berikut, stylesheet menargetkan paragraf yang sama lewat class, tetapi deklarasi inline tetap yang menang:
Browser menempatkan deklarasi inline pada tingkatan tersendiri yang melampaui semua selector, dan DevTools menampilkannya sebagai element.style. Satu-satunya yang mampu mengalahkannya dari stylesheet adalah deklarasi !important, seperti akan kita lihat pada chapter !important.
Ringkasan #
Specificity memberi bobot (a, b, c) pada setiap selector: id dihitung di kolom a, class-attribute-pseudo-class di kolom b, dan element-pseudo-element di kolom c. Perbandingan berjalan dari kiri ke kanan sehingga satu id selalu mengalahkan segudang class, universal selector tidak menambah apa pun, dan :not() hanya menghitung argumennya. Deklarasi inline berada di atas semua selector dan hanya ditundukkan oleh !important. Setelah menguasai penentu ketiga ini, kita beralih ke mekanisme yang bekerja ketika tidak ada deklarasi sama sekali: inheritance.