最常见原因是:root未定义浅色默认变量,导致var()回退为unset而白屏或透明;必须先在:root显式声明所有变量默认值,再用@media (prefers-color-scheme: dark)顶格、后置覆盖,且需加color-scheme: light dark声明以适配原生控件。

只写 @media (prefers-color-scheme: dark) 为什么没反应
最常见的情况是:CSS 文件里只写了深色分支,:root 根本没定义任何变量,默认值为空。结果在浅色系统下,var(--bg-color) 回退为 unset,body 可能透明、继承黑色,甚至白屏(尤其 Safari 12.1+)。
它不是“开关”,而是“覆盖规则”——必须先有默认值,才能被覆盖。
要确保生效,必须满足三点:
-
:root中显式声明所有主题变量的浅色默认值,比如--bg-color: #fff、--text-color: #333 -
@media (prefers-color-scheme: dark)块必须顶格、独立书写,括号内外空格不能省:@media (prefers-color-scheme: dark)✅,@media(prefers-color-scheme:dark)❌ - 该媒体查询块必须放在默认样式之后,否则会被后续无条件规则覆盖(CSS 层叠顺序决定一切)
@media (prefers-color-scheme: light) 真的需要写吗
多数场景下可省略,但不建议跳过。因为系统设置为 no-preference 时(部分 Linux 发行版或旧设备),Chrome/Firefox 默认按 light 渲染,Safari 却可能不一致。只靠 default + dark 会漏掉这个中间态。
稳妥做法是补上 light 分支,哪怕只是兜底重申默认值:
:root {
--bg-color: #fff;
--text-color: #333;
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: #fff;
--text-color: #333;
}
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
}
}
这样既明确语义,又避免 no-preference 下变量未初始化的风险。
原生控件(input、滚动条、SVG)为啥还是浅色
仅改 CSS 变量对 <input>、<select></select>、::-webkit-scrollbar、<svg></svg> 的 fill/stroke 完全无效——它们不读取自定义属性,也不自动响应 prefers-color-scheme。
必须额外加两件事:
- 在
:root或html上声明color-scheme: light dark,告诉浏览器你支持双模式,让原生控件适配系统语义色(如 CanvasText、Canvas) - 对关键控件显式重置样式,例如:
input, textarea { background: var(--bg-surface); color: var(--text-primary); },::-webkit-scrollbar-thumb { background: var(--border-secondary); }
漏掉任意一项,深色模式下就可能出现白底黑字输入框、隐形滚动条或白色 SVG 图标。
手动切换和系统偏好怎么共存不打架
用户点了「深色模式」按钮,但系统是浅色——这时候不能让 @media 覆盖 JS 设置。核心原则:手动优先,系统 fallback。
实现上靠 CSS 层叠顺序 + data 属性:
- JS 只负责设置
document.documentElement.setAttribute('data-theme', 'dark')并存入localStorage - CSS 中,手动规则写在系统媒体查询之前(利用层叠顺序压倒后者):
[data-theme="dark"] { --bg-color: #121212; } -
@media (prefers-color-scheme: dark)仅作为 fallback,放在手动规则之后 - 监听系统变化时,仅当
data-theme未设置才更新主题,避免覆盖用户选择
真正容易翻车的是:把 JS 切 class 和 media 查询混用,又没控制好顺序,结果页面在系统切换瞬间“闪回”到系统偏好,完全无视用户点击。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











