Dark Mode Toggle

Dark Mode là gì?

Dark mode là chế độ giao diện tối giúp giảm mỏi mắt trong điều kiện ánh sáng yếu. Cách triển khai hiện đại dùng CSS custom properties + localStorage để lưu lựa chọn của người dùng.

Hai chiến lược phổ biến:

  • Class toggle: thêm/bỏ class .dark trên <html> hoặc <body>
  • data-theme attribute: <html data-theme="dark"> — dễ mở rộng nhiều theme
Kết quả
<div class="app">
  <header>
    <h1>☀️ Blog của tôi</h1>
    <button id="toggle" onclick="toggleDark()">🌙 Chế độ tối</button>
  </header>
  <main>
    <article>
      <h2>Bài viết nổi bật</h2>
      <p>Đây là đoạn giới thiệu bài viết. Giao diện tự thích nghi với dark mode mà không cần CSS riêng cho từng thành phần.</p>
      <button class="btn">Đọc thêm →</button>
    </article>
    <article>
      <h2>Lập trình web hiện đại</h2>
      <p>CSS custom properties giúp việc thay đổi theme cực kỳ đơn giản chỉ với một vài dòng JavaScript.</p>
      <button class="btn">Đọc thêm →</button>
    </article>
  </main>
</div>
/* === Định nghĩa biến theo theme === */
:root {
  --bg:         #f8f9fa;
  --surface:    #ffffff;
  --text:       #212529;
  --text-muted: #6c757d;
  --border:     #dee2e6;
  --accent:     #4472c4;
  --btn-text:   #ffffff;
}

[data-theme="dark"] {
  --bg:         #121212;
  --surface:    #1e1e1e;
  --text:       #e0e0e0;
  --text-muted: #9e9e9e;
  --border:     #333333;
  --accent:     #6fa3ef;
  --btn-text:   #121212;
}

/* === Layout dùng biến, không cần media query === */
* { box-sizing: border-box; margin: 0; padding: 0; transition: background .3s, color .3s, border-color .3s; }
body { background: var(--bg); color: var(--text); font-family: sans-serif; }

header {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 1rem 1.5rem;
  display: flex; align-items: center; justify-content: space-between;
}
h1 { font-size: 1.1rem; }

#toggle {
  padding: .4rem .9rem; border: 1px solid var(--border);
  background: var(--surface); color: var(--text);
  border-radius: 20px; cursor: pointer; font-size: .85rem;
}
#toggle:hover { background: var(--accent); color: var(--btn-text); border-color: var(--accent); }

main { display: flex; gap: 1rem; padding: 1.5rem; flex-wrap: wrap; }
article {
  flex: 1 1 200px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px; padding: 1.25rem;
}
h2 { font-size: .95rem; margin-bottom: .5rem; }
p  { color: var(--text-muted); font-size: .85rem; line-height: 1.5; margin-bottom: 1rem; }
.btn {
  padding: .35rem .85rem; background: var(--accent); color: var(--btn-text);
  border: none; border-radius: 5px; cursor: pointer; font-size: .82rem;
}
const root    = document.documentElement;
const toggleBtn = document.getElementById('toggle');

// Đọc tuỳ chọn đã lưu
const saved = localStorage.getItem('site-theme');
if (saved) applyTheme(saved);

function applyTheme(theme) {
  root.setAttribute('data-theme', theme);
  if (theme === 'dark') {
    toggleBtn.textContent = '☀️ Chế độ sáng';
  } else {
    toggleBtn.textContent = '🌙 Chế độ tối';
  }
}

function toggleDark() {
  const current = root.getAttribute('data-theme');
  const next    = current === 'dark' ? 'light' : 'dark';
  applyTheme(next);
  localStorage.setItem('site-theme', next);
}

// Tự động theo hệ điều hành nếu chưa có tuỳ chọn
if (!saved) {
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  if (prefersDark) applyTheme('dark');
}

Bình luận