Dark Mode

Sistem dark theme project ini: class-based (.dark), token-driven, dengan deteksi system preference. Demo interaktif, strategi implementasi, dan panduan per-komponen.

Live Preview

Toggle untuk melihat transisi light ↔ dark dalam konteks terisolasi.

preview.php
Dashboard Overview
Sistem telah berjalan selama 14 hari tanpa gangguan. Semua metric dalam kondisi normal.
Revenue
Rp 48.2jt
+12%
Orders
1,284
+8%
Returns
23
−2%
Conversion Funnel
Visitors
100%
Product view
68%
Add to cart
31%
Checkout
18%
 · 

Implementasi

Project ini menggunakan class-based dark mode — class .dark ditambahkan ke <html>, bukan @media (prefers-color-scheme).

01

Class pada <html>

Tambah atau hapus class .dark di elemen <html>. Seluruh tree CSS merespons secara otomatis.

// tambah dark mode
document.documentElement.classList.add('dark');

// hapus dark mode
document.documentElement.classList.remove('dark');

// toggle
document.documentElement.classList.toggle('dark');
02

Deteksi system preference

Baca preferensi OS pengguna via matchMedia. Gunakan ini sebagai default sebelum user pernah memilih manual.

const mq = window.matchMedia('(prefers-color-scheme: dark)');

// cek saat ini
if (mq.matches) { ... }

// listen perubahan real-time (user ganti OS theme)
mq.addEventListener('change', e => {
  if (e.matches) document.documentElement.classList.add('dark');
  else           document.documentElement.classList.remove('dark');
});
03

Persist ke localStorage

Simpan pilihan user agar tidak kembali ke default saat halaman di-refresh. Baca dari localStorage sebelum paint pertama untuk menghindari flash.

// Letakkan di <head>, sebelum CSS lain, inline —
// supaya tidak ada FOUC (flash of unstyled content)
const saved = localStorage.getItem('theme');
const prefersDark = window.matchMedia(
  '(prefers-color-scheme: dark)'
).matches;

if (saved === 'dark' || (!saved && prefersDark)) {
  document.documentElement.classList.add('dark');
}
04

CSS: override dengan .dark

Pattern di base.css dan _base.css project ini. Semua token dark sudah tersedia di token.css.

/* light (default) */
.my-card {
  background: var(--color-bg-b1);
  color: var(--color-primary);
}

/* dark override */
.dark .my-card {
  background: var(--color-bg-d3);
  color: var(--color-primary-dark);
}

Token Swap Reference

Pasangan token light ↔ dark yang paling sering digunakan. Semua sudah terdefinisi di token.css.

Light Dark
--color-primary
--color-primary-dark
--color-secondary
--color-secondary-dark
--color-tertiary
--color-tertiary-dark
--color-bg-b1
--color-bg-d2
--color-bg-b2
--color-bg-d3
--color-bg-b3
--color-bg-d4
--color-bg-b4
--color-bg-d5

Hint: pasangan background gelap mengikuti pola d2→d3→d4 untuk page→surface→border, bukan d1 (pure black) karena terlalu kontras.

Alpine.js: Three-Mode Toggle

Snippet siap pakai untuk toggle System / Light / Dark dengan Alpine + localStorage. Ini pola yang digunakan demo di atas.

// Alpine.data() — daftarkan di alpine:init
Alpine.data('themeToggle', () => ({
  mode: localStorage.getItem('theme') || 'system',
  systemPrefersDark: window.matchMedia(
    '(prefers-color-scheme: dark)'
  ).matches,

  get isDark() {
    return this.mode === 'dark' ||
      (this.mode === 'system' && this.systemPrefersDark);
  },

  setMode(val) {
    this.mode = val;
    if (val === 'system') {
      localStorage.removeItem('theme');
    } else {
      localStorage.setItem('theme', val);
    }
    document.documentElement.classList.toggle('dark', this.isDark);
  },

  init() {
    // apply on load
    document.documentElement.classList.toggle('dark', this.isDark);

    // react to OS theme change
    window.matchMedia('(prefers-color-scheme: dark)')
      .addEventListener('change', e => {
        this.systemPrefersDark = e.matches;
        if (this.mode === 'system') {
          document.documentElement.classList.toggle(
            'dark', e.matches
          );
        }
      });
  },
}));

Checklist Komponen

wajib Gunakan token CSS (--color-bg-b*, --color-primary) — jangan hardcode hex
wajib Tambah .dark override untuk setiap warna background dan teks
wajib Test di kedua mode sebelum commit — hover, focus, disabled state
wajib Border dan divider juga perlu .dark override (sering terlewat)
wajib Icon SVG: pastikan stroke/fill menggunakan currentColor atau token
saran Gambar: sediakan versi gelap jika diperlukan (.dark img { filter: brightness(.85) })
saran Hindari box-shadow dengan warna solid — pakai rgb(0 0 0 / opacity) agar tetap terlihat di dark
saran Scrollbar custom (base.css sudah ada) — cek di browser berbeda