HTML CSS JavaScript

Belum masuk. Progresmu tidak tersimpan.

Masuk
  • Bab 6 dari 7
  • 30 menit
  • 1 latihan kode
  • 4 soal kuis

Studi Kasus: Tombol Mode Gelap dari Tiga Berkas

Di bab ini (9 bagian)
  1. Tujuan
  2. Apa yang kita bangun
  3. Langkah 1: index.html, struktur dan titik koneksi
  4. Langkah 2: style.css, dua tema disiapkan duluan
  5. Langkah 3: script.js, merekatkan klik dan tampilan
  6. Kenapa localStorage harus dibungkus try...catch?
  7. Hasil akhirnya
  8. Kesalahan umum
  9. Rangkuman

Tujuan bab ini

  • Membangun proyek mini tombol mode gelap dari tiga berkas: index.html, style.css, dan script.js.
  • Menyiapkan tema dengan CSS variables dan selector atribut [data-tema="gelap"].
  • Menghubungkan klik tombol ke perubahan dataset, teks tombol, dan tampilan.
  • Menyimpan pilihan tema dengan localStorage secara aman di dalam try...catch.

Apa yang kita bangun

Halaman sederhana berisi judul, paragraf, dan satu tombol. Tampilan bawaannya terang. Sekali klik, seluruh halaman jadi gelap dan teks tombol berubah dari "Mode Gelap" jadi "Mode Terang". Klik lagi, balik terang. Bonusnya: saat halaman dibuka ulang, pilihan terakhir diingat.

Proyek kecil ini sengaja dipilih karena memakai semua koneksi yang sudah kamu pelajari: HTML memanggil CSS, HTML memanggil JS, JS membaca DOM, dan JS memicu aturan CSS.

mode-gelap/
|-- index.html      <- struktur
|-- css/
|   \-- style.css   <- tampilan
\-- js/
    \-- script.js   <- perilaku

Struktur berkas dan empat koneksi proyek mode gelap

Di slide, saklarnya adalah class gelap pada <body>. Di bab ini kita naik satu tingkat: saklarnya berupa atribut data-tema pada elemen <html>, dan warnanya disimpan di CSS variables. Hasilnya sama, tapi lebih rapi kalau nanti temanya bertambah.

Langkah 1: index.html, struktur dan titik koneksi

<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Demo Mode Gelap</title>
  <link rel="stylesheet" href="css/style.css">
  <script src="js/script.js" defer></script>
</head>
<body>
  <h1 id="judul">Halo, Dunia Web</h1>
  <p class="keterangan">Klik tombol untuk mengganti tema halaman.</p>
  <button class="tombol" id="saklar" type="button" aria-pressed="false">Mode Gelap</button>
</body>
</html>

Perhatikan tiga titik koneksinya:

  1. <link rel="stylesheet" href="css/style.css"> di <head>. Path-nya relatif terhadap index.html, jadi browser mencari folder css di sebelahnya.
  2. <script src="js/script.js" defer> juga di <head>. Berkat defer, script diunduh sambil parsing dan baru dijalankan setelah tombol ada di DOM (ingat bab sebelumnya).
  3. id="saklar" adalah alamat yang dipakai JavaScript, sedangkan class="tombol" adalah alamat yang dipakai CSS. Satu elemen boleh punya keduanya.

aria-pressed memberi tahu screen reader bahwa tombol ini bisa "ditekan" dan "dilepas". Bonus aksesibilitas yang murah.

Langkah 2: style.css, dua tema disiapkan duluan

:root {
  --latar: #ffffff;
  --teks: #1b2030;
  --aksen: #e34f26;
}

[data-tema="gelap"] {
  --latar: #1b2030;
  --teks: #f3f5fa;
  --aksen: #f7b267;
}

body {
  background: var(--latar);
  color: var(--teks);
  font-family: Arial, sans-serif;
  transition: background 0.3s, color 0.3s;
}

#judul { font-size: 32px; }
.keterangan { line-height: 1.6; }

.tombol {
  background: var(--aksen);
  color: #1b2030;
  border: 0;
  padding: 12px 24px;
  border-radius: 8px;
  cursor: pointer;
}

Cara bacanya:

  • :root adalah elemen <html>. Di sini kita mendefinisikan variabel untuk tema terang. Variabel CSS diwariskan ke semua anak, jadi body dan .tombol bisa memakainya.
  • [data-tema="gelap"] berlaku untuk elemen yang punya atribut data-tema="gelap". Saat JavaScript memasang atribut itu di <html>, nilai ketiga variabel ditimpa. Specificity :root dan selector atribut sama-sama satu (0,1,0), jadi aturan yang ditulis belakangan yang menang. Itulah kenapa blok gelap ditaruh di bawah.
  • body dan .tombol tidak tahu-menahu soal tema. Mereka cuma bilang "pakai --latar", dan nilai --latar yang berubah.
  • transition membuat perpindahan warna halus selama 0,3 detik tanpa satu baris JavaScript pun.

Hasilnya saat tema gelap aktif: latar halaman biru tua #1b2030, teks hampir putih, dan tombol berubah dari oranye ke kuning keemasan.

Langkah 3: script.js, merekatkan klik dan tampilan

const saklar = document.getElementById("saklar");
const akar = document.documentElement; // elemen <html>

function terapkan(tema) {
  const gelap = tema === "gelap";
  if (gelap) akar.dataset.tema = "gelap";
  else delete akar.dataset.tema;
  saklar.textContent = gelap ? "Mode Terang" : "Mode Gelap";
  saklar.setAttribute("aria-pressed", String(gelap));
}

let tersimpan = null;
try {
  tersimpan = localStorage.getItem("tema");
} catch (e) {
  // storage diblokir, pakai tema bawaan
}
terapkan(tersimpan);

saklar.addEventListener("click", () => {
  const baru = akar.dataset.tema === "gelap" ? "terang" : "gelap";
  terapkan(baru);
  try {
    localStorage.setItem("tema", baru);
  } catch (e) {
    // tidak bisa menyimpan, tema tetap berganti untuk sesi ini
  }
});

Langkah demi langkah:

  1. Ambil elemen. getElementById("saklar") membaca DOM lewat penanda dari HTML. document.documentElement adalah <html>.
  2. dataset. Properti akar.dataset.tema adalah cerminan atribut data-tema. Menulis akar.dataset.tema = "gelap" sama dengan memasang data-tema="gelap"; delete akar.dataset.tema menghapus atributnya. Nama data-nama-panjang jadi dataset.namaPanjang (camelCase).
  3. Fungsi terapkan. Satu fungsi untuk satu pekerjaan: menyamakan atribut, teks tombol, dan aria-pressed dengan tema. Dipakai saat halaman dibuka dan saat tombol diklik, jadi logikanya tidak ditulis dua kali.
  4. Baca pilihan lama. localStorage.getItem("tema") mengembalikan string atau null bila belum pernah disimpan.
  5. Event klik. Tentukan tema baru, terapkan, lalu simpan.

Perhatikan: JavaScript tidak menulis satu kode warna pun. Ia cuma mengganti satu atribut, dan CSS yang memutuskan semua warnanya.

Kenapa localStorage harus dibungkus try...catch?

localStorage terlihat sepele, tapi aksesnya bisa melempar error, bukan sekadar mengembalikan null. Contohnya:

  • Pengguna memblokir cookie dan data situs di pengaturan browser.
  • Halaman berjalan di iframe sandbox tanpa izin storage. Latihan kode di Ruang Materi termasuk kasus ini.
  • Kuota penuh, sehingga setItem melempar QuotaExceededError.

Tanpa try...catch, error di baris pertama script menghentikan seluruh script. Tombol tidak dipasangi event dan fitur mode gelap mati total, padahal fitur intinya tidak butuh penyimpanan sama sekali. Dengan try...catch, kegagalan menyimpan cuma berarti "pilihan tidak diingat", dan tombol tetap berfungsi.

Hasil akhirnya

Buka index.html di browser:

  • Pertama kali dibuka, halaman terang dan tombol bertuliskan "Mode Gelap".
  • Klik sekali: <html> mendapat data-tema="gelap" (cek di panel Elements), warna berpindah halus, tombol jadi "Mode Terang".
  • Refresh: halaman langsung gelap karena pilihan dibaca dari localStorage.
  • Klik lagi: atribut hilang, semua kembali terang.

Mungkin kamu melihat kedipan terang sesaat sebelum tema gelap terpasang saat refresh. Itu karena script defer baru jalan setelah parsing selesai. Untuk proyek belajar ini tidak masalah; situs besar biasanya menaruh script kecil khusus tema langsung di <head>.

Kesalahan umum

  • Nama atribut beda. CSS menulis [data-theme="gelap"], JS menulis dataset.tema. Atribut terpasang, tapi tidak ada aturan yang cocok.
  • Blok [data-tema="gelap"] ditaruh di atas :root. Karena specificity-nya sama, :root yang belakangan menang dan tema gelap tidak pernah terlihat.
  • Lupa var(). Menulis background: --latar; tidak valid, jadi diabaikan browser.
  • localStorage tanpa try...catch. Satu error di awal mematikan seluruh script.
  • Menulis warna di JavaScript (body.style.background = "#1b2030"). Bekerja, tapi keputusan tampilan jadi tersebar di dua berkas.

Rangkuman

  • index.html punya tiga titik koneksi: <link> ke CSS, <script defer> ke JS, dan id/class sebagai alamat elemen.
  • style.css menyiapkan kedua tema lewat CSS variables; [data-tema="gelap"] menimpa nilainya.
  • script.js cuma mengganti dataset.tema, teks tombol, dan aria-pressed, lalu CSS mengurus warnanya.
  • Akses localStorage selalu dibungkus try...catch karena bisa diblokir.

Selesaikan bab ini

  1. 1. Baca materi
  2. 2. Latihan kode (1)
  3. 3. Kuis (4 soal)

Latihan kode: Tombol mode gelap dengan data-tema

Lengkapi proyek mode gelap:

  • Di CSS, tambahkan aturan [data-tema="gelap"] di bawah :root yang mengubah --latar jadi #1b2030 dan --teks jadi #f3f5fa.
  • Di JS, saat #saklar diklik: pasang data-tema="gelap" pada document.documentElement bila belum gelap, atau hapus atributnya bila sudah gelap (pakai dataset).
  • Teks tombol jadi Mode Terang saat gelap aktif dan Mode Gelap saat terang.
  • Simpan pilihan ke localStorage dengan kunci "tema", dibungkus try...catch. Latihan ini berjalan di iframe sandbox yang memblokir storage, jadi tanpa itu script langsung error.

Cek pemahaman

  1. 1Tombol diklik, panel Elements menunjukkan `data-tema="gelap"` sudah terpasang di `<html>`, tapi warna tidak berubah. Penyebab paling mungkin?

  2. 2Warna latar apa yang dipakai `body`?

    <html data-tema="gelap">
    <head>
      <style>
        :root { --latar: #ffffff; }
        [data-tema="gelap"] { --latar: #1b2030; }
        body { background: var(--latar); }
      </style>
    </head>
    <body>...</body>
    </html>
  3. 3Atribut `data-tema="gelap"` pada `<html>` bisa dibaca di JavaScript lewat `document.documentElement.dataset.tema`.

  4. 4Kenapa akses `localStorage` di `script.js` dibungkus `try...catch`?