真正可靠且推荐的做法,其实只有一种:直接使用 window.matchMedia('(prefers-color-scheme: dark)') 实时检测系统是否切换到暗黑模式;一旦状态发生变化,就要监听 change 事件,及时更新 data-theme 和 localStorage,并且在页面初始化阶段先完成一次同步设置。CSS 部分则通过 [data-theme="dark"] 统一接管主题变量切换;而媒体查询本身只负责样式判断,不会主动触发 JavaScript 行为。

直接使用 window.matchMedia('(prefers-color-scheme: dark)') 进行检测,这是判断用户暗黑模式偏好的唯一可靠方式。它返回的是一个 MediaQueryList 对象,.matches 属性可以实时反映系统当前是否启用了深色模式,不能依赖 class 名称或 localStorage 去伪造判断结果。
必须监听变化,不能只检测一次
很多开发者在页面加载时只读取一次状态,结果用户后来在系统设置中切换了深色模式,网页却没有任何响应。正确方式是绑定 change 事件监听系统主题变化:
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', e => {
const isDark = e.matches;
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});初始化时也要同步检查
页面首次打开时,也要立刻根据系统主题偏好设置好当前主题,并在用户没有手动选择的前提下写入 localStorage:
- 先读取
localStorage.getItem('theme'),如果已有值就优先使用; - 如果没有值,再读取
mediaQuery.matches,然后把结果写回 localStorage。
CSS 中使用属性选择器响应主题
不要维护两套重复样式,推荐统一使用 html[data-theme="dark"] 控制 CSS 变量切换:
:root {
--bg: #ffffff;
--text: #333333;
}
html[data-theme="dark"] {
--bg: #121212;
--text: #e0e0e0;
}
body {
background-color: var(--bg);
color: var(--text);
transition: background-color 0.3s, color 0.3s;
}注意:媒体查询本身不会直接触发 JS 切换@media (prefers-color-scheme: dark) 能处理的是纯 CSS 层面的样式变化,但它无法更新 data-theme 属性,也不会同步按钮状态,更不会自动改写 localStorage。想要真正实现“自动适配系统深色模式 + 用户手动切换主题 + 本地持久化记忆”这整套逻辑,JavaScript 监听和 DOM 更新都不能缺少。
逻辑并不复杂,但这个细节非常容易被忽略。
