暗色模式已经成了现代网页和应用的标配功能。很多人第一反应是用 JavaScript 来切换主题,但其实——只用 CSS 就能实现一个相当优雅的暗色模式,而且性能更好、代码更少。

核心武器:CSS 自定义属性

CSS 自定义属性(也叫 CSS 变量)是整个方案的基石。我们把所有颜色集中定义在 :root 里,主题切换时只需要改变变量的值。

:root {
  --bg: #ffffff;
  --text: #1e293b;
  --card: #f8fafc;
  --border: #e2e8f0;
  --primary: #2563eb;
}

[data-theme="dark"] {
  --bg: #0f172a;
  --text: #e2e8f0;
  --card: #1e293b;
  --border: #334155;
  --primary: #60a5fa;
}

这种集中管理的方式让主题切换变得极其干净——只需在一个地方定义颜色映射,整个页面的所有元素都会自动响应。

自动跟随系统偏好

使用 prefers-color-scheme 媒体查询,CSS 可以自动检测用户的系统主题设置:

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f172a;
    --text: #e2e8f0;
    --card: #1e293b;
    --border: #334155;
    --primary: #60a5fa;
  }
}

:not([data-theme="light"]) 这个选择器很关键——它确保如果用户手动选了亮色模式,系统偏好不会覆盖用户的选择。

平滑过渡

直接切换颜色会显得很突兀。加一个简单的过渡就能让体验提升一个档次:

body {
  background: var(--bg);
  color: var(--text);
  transition: background 0.3s ease, color 0.3s ease;
}
🎨 过渡效果的细节:
只对 background 和 color 做过渡,不要用 transition: all——会让边框、阴影等也跟着动画,显得拖泥带水。

切换按钮

JS 只需要做一件事:切换 data-theme 属性,并记住用户偏好。

const btn = document.querySelector('.theme-toggle');
const html = document.documentElement;

btn.addEventListener('click', () => {
  const current = html.dataset.theme;
  const next = current === 'dark' ? 'light' : 'dark';
  html.dataset.theme = next;
  localStorage.setItem('theme', next);
});

// 读取保存的偏好
const saved = localStorage.getItem('theme');
if (saved) html.dataset.theme = saved;

需要处理的细节

暗色模式下有几个容易忽略的地方:

完整方案的优先级

最终的主题优先级应该是:用户手动选择 > 系统偏好 > 默认值。这个顺序保证了用户始终拥有最终控制权。

好的暗色模式不是把白底变黑底那么简单,而是对整个视觉层次的一次重新设计。