@media (prefers-color-scheme: dark) 可原生检测系统暗黑模式,仅响应系统级外观设置(如macOS/Windows主题),无需JS;不支持旧版Safari。

怎么用 @media (prefers-color-scheme) 检测系统暗黑模式
浏览器原生支持,不用 JS 也能响应系统主题切换。关键就一条媒体查询:@media (prefers-color-scheme: dark) 或 @media (prefers-color-scheme: light)。
注意:它只读取系统级设置(macOS 设置 > 通用 > 外观 / Windows 设置 > 个性化 > 颜色 > 默认应用模式),不是浏览器自带的“深色主题”开关,也不是网页自己存的 theme 状态。
- 不支持低版本 Safari(
- Chrome 和新版 Edge 从 76 起稳定支持,但早期版本有 bug:切系统主题后不会重触发媒体查询,需刷新页面
- 可以嵌套在
@layer或普通样式块里,但不能写在@import后面(会失效)
为什么直接覆盖全局颜色变量比重复写选择器更可靠
很多人一上来就给每个元素加 [data-theme="dark"] .btn 这类选择器,结果维护爆炸、权重打架、漏写就白搭。用 CSS 自定义属性(--color-bg、--color-text)统一收口才是正解。
核心逻辑是:媒体查询只负责“设变量”,所有组件样式用变量表达,一次声明,处处响应。
立即学习“前端免费学习笔记(深入)”;
- 变量必须定义在 :root 下才可被媒体查询覆盖;写在局部选择器里会被忽略
- 暗黑模式下优先用
hsl()或lab()调色,避免#121212这种硬编码——亮度/对比度容易不达标 - 别忘了
color-scheme: dark声明,告诉浏览器表单控件也走暗色渲染(否则 checkbox、input 边框可能还是亮色)
:root {
--color-bg: #fff;
--color-text: #333;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1e1e1e;
--color-text: #e0e0e0;
color-scheme: dark;
}
}
body {
background: var(--color-bg);
color: var(--color-text);
}
prefers-color-scheme 不生效的三个高频原因
写对了语法却没反应?大概率卡在这几个地方:
- 本地开发用
file://协议打开 HTML —— Chrome 和 Safari 会禁用该媒体查询(安全限制),必须走http://或https://(哪怕本地启个python3 -m http.server) - CSS 文件被缓存,而你改的是媒体查询里的变量值 —— 浏览器可能复用旧 CSS,强制刷新(
Cmd+Shift+R)或关掉 DevTools 的 “Disable cache” 再试 - 系统开启了“自动切换(日出日落)”,但当前时间刚好卡在临界点附近,导致检测不稳定;建议先手动切到“深色”或“浅色”再验证
要不要加 JS fallback 来手动切主题
要,但只在必要时。媒体查询本身无法响应用户主动点击“切换主题”按钮,这时候得靠 JS 改 document.documentElement.dataset.theme,再配合 CSS 变量兜底。
重点不是“加不加 JS”,而是“怎么不破坏原生体验”:
- 初始化时优先读
window.matchMedia('(prefers-color-scheme: dark)').matches,而不是直接读 localStorage - 把用户选择存在
localStorage,但只在 JS 检测到与系统不一致时才覆盖变量 —— 避免“用户切了暗黑,系统又切回亮色,页面还卡在暗色” - 不要用
class控制主题(如),会导致媒体查询和 JS 两套逻辑并存,后期难收敛
真正麻烦的从来不是怎么切,而是怎么让“系统偏好”“用户偏好”“服务端预判”三者不互相覆盖。变量 + 媒体查询 + 有状态的 JS 初始化,这个组合最稳。


















