@media (prefers-color-scheme: dark) 本身只是开关,真正生效需三者齐备:meta color-scheme、html color-scheme 声明、媒体查询内用系统色重定义 CSS 变量,缺一则原生控件不响应。

@media (prefers-color-scheme: dark) 本身不会让页面变暗,它只是个开关;真正起作用的是你有没有在它里面正确重定义 CSS 变量,以及是否配合 color-scheme 和 <meta name="color-scheme"> 让原生控件也响应。
为什么写了 @media (prefers-color-scheme: dark) 却没反应
不是语法错,而是三个常见断点卡住了:
-
@media (prefers-color-scheme: dark)规则写在了:root默认变量定义之前——CSS 按顺序执行,后声明的变量会覆盖前面媒体查询设的值 -
:root里根本没初始化变量,比如漏了--border-color,深色模式下var(--border-color)直接 fallback 到浏览器默认浅灰,在深背景上几乎看不见 - 只改了
body背景和文字色,但<input>、<select>、滚动条这些原生元素仍白底黑字——它们不读你的 CSS 变量,只认color-scheme+<meta>的组合信号
必须同时写的三样东西
缺一不可,少一个,<input> 就不会变暗,滚动条 thumb 就还是亮的:
-
<meta name="color-scheme" content="light dark">放在<head>最顶部(静态写死,JS 动态插入无效) -
html { color-scheme: light dark; }—— 这个声明必须存在,且不能被!important或更高优先级规则压制 -
@media (prefers-color-scheme: dark) { :root { --bg: Canvas; --text: CanvasText; } }—— 所有颜色变量必须用系统语义色(Canvas、CanvasText)或明确的 fallback 值,别硬写#121212
变量命名和覆盖的实操细节
别以为起个 --dark-bg 就行了,CSS 变量是“覆盖式重赋值”,不是“条件分支”:
立即学习“前端免费学习笔记(深入)”;
- 所有变量必须先在顶层
:root显式定义默认值,例如--bg: #ffffff;,否则 light 模式下var(--bg)是未定义状态 - 深色规则里只能用完全相同的变量名重赋值,比如
--bg→Canvas,不能新增--bg-dark,否则其他地方var(--bg-dark)在 light 模式下直接失效 - 每个
var()都建议加 fallback,但 fallback 是固定值,不会随主题变:background-color: var(--bg, #ffffff);—— 它只兜底“变量未定义”,不兜底“主题切换” - SVG 图标要响应主题,得用
fill: var(--icon-fill);或fill: currentColor;,不能靠filter: invert(1),后者在高对比度模式下会彻底不可读
容易被忽略的兼容性坑
看似简单的媒体查询,在真实环境里最常栽在加载时机和降级逻辑上:
- Safari 12.1 之前不支持嵌套媒体查询,所以
@media (max-width: 768px) { @media (prefers-color-scheme: dark) { ... } }整块会被忽略 - SSR 渲染首屏时,若服务端没同步用户系统偏好,可能出现“先亮后暗”的闪动——解决方案是内联关键 CSS,并在 HTML 根节点加
data-color-scheme="dark"属性做初始标记 -
Canvas等系统色不支持 alpha,需要半透明时得用color-mix(in srgb, CanvasText 70%, transparent)(Chrome 111+),老浏览器 fallback 到变量 - 部分 Android WebView 对
<meta name="color-scheme">支持不稳定,必须把color-scheme: light dark同时写在html和:root上双保险


















