必须先在 :root 中定义所有 CSS 变量的浅色默认值,否则浅色系统下变量未声明会导致 var() 回退为 unset,引发白屏或黑屏;深色规则需后置覆盖,且需显式重置原生控件、SVG 和滚动条样式,并遵循深色专用配色逻辑。

只写 @media (prefers-color-scheme: dark) 但没在 :root 里定义默认值,页面在浅色系统下大概率白屏或黑屏——这不是浏览器 bug,是 CSS 变量未声明导致的回退行为。
为什么 :root 必须先定义所有变量的浅色默认值
变量不是“全局常量”,它依赖声明时机和作用域。如果只在 @media (prefers-color-scheme: dark) 块里写 --color-bg: #121212,那在浅色系统下这个变量根本不存在,var(--color-bg) 就会回退为 unset。
后果很直接:
-
body { background: var(--color-bg); }→ 实际渲染成background: unset,可能继承父级透明或黑色(尤其 Safari 12.1+) -
color: var(--text-primary, #333)→ 若--text-primary未声明,#333仍生效;但若漏写 fallback,就变成color: unset,文字消失 - 构建工具(如 PostCSS)若提前展开变量,还会让整个媒体查询块失效
深色规则必须放在所有默认样式之后
CSS 层叠不看媒体查询“优先级”,只看声明顺序。哪怕你写了 @media (prefers-color-scheme: dark),只要后面还有无条件的 :root { --color-bg: #fff },深色值就会被覆盖。
立即学习“前端免费学习笔记(深入)”;
正确顺序只能是:
- 第一步:在
:root中完整定义所有变量的浅色值(例如--bg-surface: #fff、--text-primary: #1a1a1a) - 第二步:再写
@media (prefers-color-scheme: dark) { :root { ... } },仅做覆盖 - 第三步(可选但推荐):加
@media (prefers-color-scheme: light) { :root { ... } },兜底no-preference场景
别嵌套:@media (max-width: 768px) { @media (prefers-color-scheme: dark) { ... } } 在旧版 Safari 中整块被忽略。
原生控件和 SVG 不会自动变色,必须显式声明
改了 --text-primary 和 --bg-surface,<input>、<select></select>、::-webkit-scrollbar、svg fill 这些依然用系统默认样式——它们不读 CSS 变量。
关键补救动作:
- 在
:root或html上加color-scheme: light dark,通知浏览器你支持双模式 - 重置表单控件:比如
input, select, textarea { background: var(--bg-surface); color: var(--text-primary); border-color: var(--border-default); } - SVG 要加
fill="currentColor"或用fill: var(--icon-primary),并在媒体查询中同步赋值 - 滚动条需单独写
::-webkit-scrollbar-track { background: var(--bg-surface); }
颜色值不能简单取反,深色模式有自己的一套对比逻辑
把浅色下的 #333 直接换成 #ccc,在深灰背景上几乎看不见;把 #007bff 按比例变亮,可能在深色下饱和度过高、刺眼。
真正可用的深色配色习惯:
- 主背景用
#121212(Material)、#1e1e1e(VS Code),别用纯黑#000 - 正文文字用
#e0e0e0~#d0d0d0,小字号慎用#fff - 边框/分割线用
rgba(255, 255, 255, 0.08)或#303030,别用#333 - 强调色检查 WCAG 对比度:深色下
#2196f3常不够,换成#42a5f5更稳
最易被忽略的一点:系统切换后页面不会自动重绘——除非刷新或用 window.matchMedia 监听并触发强制重排。但注意,监听本身不能替代初始 CSS 加载时的正确结构。


















