Toggle Tema 3 Arah: System, Light, Dark — Bukan Cuma prefers-color-scheme
Situs ini punya tombol tema di navbar yang, kalau diklik, siklus lewat tiga state: system → light → dark → balik ke system. Awalnya saya kira cukup ngikutin prefers-color-scheme doang tanpa kontrol manual, tapi ternyata ada alasan kuat buat nambahin toggle eksplisit.
Kenapa nggak cukup cuma prefers-color-scheme? Karena beberapa pengunjung browser/OS-nya di-set light secara default (misalnya laptop kantor yang policy-nya dipaksa light), tapi mereka pribadi lebih nyaman baca teks panjang dalam gelap, atau sebaliknya. Static site nggak tahu preferensi personal itu kalau cuma ngandelin signal OS.
Implementasinya pakai custom variant Tailwind. Alih-alih pakai dark: bawaan Tailwind yang defaultnya ngikutin media query langsung, saya override lewat @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *)); di global.css. Jadi kelas dark: di seluruh komponen ngikutin atribut data-theme di elemen <html>, bukan raw media query lagi.
Logikanya di client-side script (ThemeToggle.astro): ada tiga mode (system, light, dark). Kalau mode-nya system, resolved theme-nya ngikutin window.matchMedia('(prefers-color-scheme: dark)') — jadi tetep ngikutin OS secara default. Begitu user klik toggle, mode-nya disimpan ke localStorage dan atribut data-theme di-set langsung, override signal OS.
Detail kecil yang saya jaga: semua akses localStorage dan matchMedia dibungkus try/catch. Di beberapa environment (private browsing mode tertentu, browser extension yang strict), API ini bisa throw error kalau diakses langsung. Situs statis harus tetap render dengan benar meskipun API itu nggak tersedia, jadi fallback-nya diem-diem aja ke default tanpa nge-crash halaman.
Listener buat perubahan OS runtime juga saya pasang — kalau mode masih system dan user ganti preferensi OS-nya di tengah sesi browsing (bukan reload halaman), tema situsnya ikut update otomatis tanpa refresh.
Awalnya saya kira solusi paling sederhana adalah nggak punya toggle sama sekali. Ternyata versi yang lebih baik bukan “sederhana secara absolut”, tapi “ngasih kontrol ke user tanpa maksa mereka setting apapun” — default-nya tetep ngikutin OS, tapi override-nya tetep tersedia buat yang butuh.