Mengganti skema warna situs tanpa custom properties berarti mengubah nilai warna di belasan tempat sekaligus — dan satu tempat yang terlewat membuat tema tampak tidak konsisten. Custom properties, yang mekanisme dasarnya telah kita bahas pada Chapter Custom Properties , menyelesaikan persoalan tersebut dengan cara menyimpan palet pada sekumpulan nama, lalu membiarkan setiap komponen membaca nilainya melalui var(). Chapter ini berfokus pada penerapan pola tersebut untuk theming: satu set variabel default, satu set penimpa per tema, dan satu atribut sebagai sakelar tema. Hasilnya, memindah seluruh halaman ke mode gelap hanya berarti mengubah satu atribut.

Palet pada :root, Penimpa per Tema #

Strategi dasarnya tiga langkah. Pertama, deklarasikan palet default pada selector :root sehingga variabel mewarisi ke seluruh dokumen. Kedua, tulis komponen yang membaca variabel tersebut — komponen tidak pernah menyebut warna secara langsung. Ketiga, tentukan blok tema yang menimpa variabel; tema menimpa nilai variabelnya, bukan properti tiap komponen:

css
:root {
  --tema-latar: rgb(248, 250, 252);
  --tema-teks: rgb(30, 41, 59);
  --tema-aksen: rgb(2, 132, 199);
}

[data-tema="gelap"] {
  --tema-latar: rgb(15, 23, 42);
  --tema-teks: rgb(226, 232, 240);
  --tema-aksen: rgb(56, 189, 248);
}

Mari kita telaah kedua blok itu:

  1. :root: selector yang menunjuk elemen <html>. Variabel yang didefinisikan di sini diwarisi oleh seluruh pohon dokumen, sehingga berfungsi sebagai tema default.
  2. [data-tema="gelap"]: attribute selector yang menimpa tiga variabel pada elemen pembawa atribut tersebut — beserta seluruh keturunannya, karena custom properties diwariskan.
  3. Hanya variabel yang berubah: tidak ada satu pun properti komponen yang disentuh blok tema; perubahan merambat otomatis ke setiap pembaca var().

Komponen Membaca, Atribut Memutuskan #

Dengan palet siap, komponen cukup memakai var(). Pada demo chapter ini, kartu membaca tiga variabel tema, dan dua kartu diberi atribut data-tema berbeda sehingga keduanya tampil dengan palet berbeda dari stylesheet yang sama:

css
.papan {
  padding: 16px;
  border-radius: 12px;
  background-color: var(--tema-latar);
  color: var(--tema-teks);
}

.papan .aksen {
  color: var(--tema-aksen);
}

Mekanisme pentingnya ada pada titik resolusi: nilai var(--tema-teks) dievaluasi pada elemen pemakai, bukan pada tempat variabel didefinisikan. Kartu di dalam wadah data-tema="gelap" membaca nilai milik wadah; kartu di luar wadah membaca nilai :root. Satu aturan .papan melayani semua tema — inilah bedanya dengan menulis dua set aturan komponen.

Perhatikan pula konvensi penamaannya: semua variabel tema memakai awalan --tema- sehingga blok penimpa terlihat utuh dan dapat dipindahkan dengan ringkas. Awalan semacam ini juga mencegah benturan dengan variabel milik komponen — --lebar-sidebar tidak boleh berubah karena tema berganti, sedangkan --tema-* memang dirancang untuk berubah.

html index.html
<article class="papan" data-tema="terang">
  <h2 class="aksen">Catatan Pagi</h2>
  <p>Tema terang memakai palet default dari :root.</p>
</article>
<article class="papan" data-tema="gelap">
  <h2 class="aksen">Catatan Malam</h2>
  <p>Tema gelap menimpa tiga variabel pada wadahnya.</p>
</article>

Hasil di Browser:

text
Papan pertama (data-tema="terang") berlatar putih kebiruan
(background-color: rgb(248, 250, 252)) dengan teks abu-abu gelap
(color: rgb(30, 41, 59)) dan judul biru (color: rgb(2, 132, 199)).
Papan kedua (data-tema="gelap") berlatar biru sangat gelap
(background-color: rgb(15, 23, 42)) dengan teks putih kebiruan
(color: rgb(226, 232, 240)) dan judul biru muda (color: rgb(56, 189, 248)).
Kedua papan memakai aturan .papan yang sama — hanya atribut yang berbeda.

Melengkapi Sistem Tema #

Tiga pelengkap membuat pola ini siap dipakai pada proyek nyata. Pertama, warna sistem: media query @media (prefers-color-scheme: dark) dapat menjadi penimpa default ketika pengguna tidak memilih tema, dan fungsi light-dark() (Baseline 2024) menyediakan jalur ringkas untuk nilai ganda. Kedua, transisi antartema: secara default custom properties tidak dapat dianimasikan; deklarasikan nama variabelnya dengan @property — Baseline 2024 — untuk memberi tipe dan membuat perubahan variabel dapat ditransisi. Ketiga, cakupan tema: karena variabel mewarisi, tema dapat dipasang pada bagian halaman tertentu saja, misalnya panel gelap di dalam halaman terang, tanpa JavaScript sama sekali.

Demo lengkap membawa data-expected pada kedua papan dan judul aksennya, memverifikasi bahwa warna terurai dari variabel tema yang berbeda pada setiap wadah.