@media (prefers-color-scheme: dark) 仅响应系统主题变化,不感知 localstorage 或用户点击,需配合 html[data-theme="dark"] 才能共存并优先覆盖;变量须先定义默认值再用媒体查询和 data-theme 双重覆盖,且所有颜色必须用 var() 引用。

只用 @media (prefers-color-scheme: dark) 覆盖 :root 变量,就能让页面随系统自动变暗——但用户点“手动切深色”时没反应,是因为媒体查询不存状态、也不响应 JS 操作。
为什么 @media (prefers-color-scheme: dark) 不能单独用
它只监听系统设置变化,不感知 localStorage 或用户点击;刷新后一切回退;无法覆盖用户主动选择。常见错误现象:html 上加了 class="dark",但 CSS 里没写 html.dark :root 覆盖变量,结果颜色还是亮的。
-
@media (prefers-color-scheme: dark)必须写完整,不能简写成@media (prefers-color-scheme),否则无效 - 它只在初始渲染和系统主题切换时触发,JS 改
document.documentElement.classList完全不影响它 - 变量必须定义在
:root下,且默认值(亮色)要先声明,再用媒体查询覆盖——反过来会导致未匹配时变量为unset
怎么让 :root 同时响应系统 + 手动切换
关键不是二选一,而是共存:媒体查询负责兜底初始值,html[data-theme="dark"] 负责接管用户选择。所有变量覆盖必须写两遍:
:root {
--bg: #fff;
--text: #333;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--text: #e0e0e0;
}
}
html[data-theme="dark"] :root {
--bg: #121212;
--text: #e0e0e0;
}
- 优先级上,
html[data-theme="dark"] :root高于媒体查询,能压过它 - 变量名必须语义化,比如
--bg-primary,别用--dark-bg-500这种带视觉暗示的命名 - 不要在变量里塞
rgba()或透明度,暗色下叠加容易发灰;改用hsl()或独立--opacity控制
如何初始化并同步 data-theme 和 localStorage
页面加载时,得先读 localStorage.getItem('theme'),有值就设 document.documentElement.setAttribute('data-theme', value);没值才 fallback 到 matchMedia('(prefers-color-scheme: dark)').matches。
- 必须在 HTML 渲染前执行(比如放在
<script></script>内联在末尾),否则首屏会闪白 - 监听系统变化时,只在用户没手动选过主题时才更新
data-theme,避免覆盖用户偏好 - 给
加<meta name="color-scheme" content="light dark">,否则原生控件(如<input>、滚动条)不会自动适配
为什么不用 class="dark" 而用 data-theme
data- 属性更语义清晰,且不会和第三方 CSS 类冲突;更重要的是,html.dark :root 的优先级低于 @media,而 html[data-theme="dark"] :root 可以稳定压过它,确保手动切换必生效。
真正容易被忽略的是:所有用到颜色的地方,都得用 var(--bg),硬编码 #121212 就等于绕过整套机制——哪怕变量定义得再漂亮,也白搭。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











