必须先在:root声明浅色默认变量,再用@media (prefers-color-scheme: dark)覆盖,否则浅色模式下变量未定义导致白屏或文字不可读;原生控件需color-scheme声明才能适配。

直接用 @media (prefers-color-scheme: dark),但必须配合 :root 中的默认变量声明,否则页面可能白屏或文字不可读。
为什么 @media (prefers-color-scheme: dark) 单独写会出问题
这个媒体查询本身不提供默认值——它只在系统启用深色模式时生效。如果 :root 里没先定义浅色变量,而只在媒体查询里重设深色值,那么在浅色模式下 var(--bg) 就是未定义状态,浏览器 fallback 到透明或初始值,常见表现是背景全白、文字消失、边框变灰。
- 错误写法:
@media (prefers-color-scheme: dark) { :root { --bg: #121212; } }(漏了顶层:root { --bg: #ffffff; }) - 正确顺序:先写
:root默认值 → 再写@media覆盖 → 两者不能颠倒 - 注意
no-preference场景(如某些 Linux 发行版或禁用主题的浏览器),也会走默认分支,所以默认值必须可用
:root 变量声明位置和作用域限制
CSS 变量的作用域是“声明处生效”,@media 块内定义的变量仅在该块内有效,无法被外部选择器读取。这意味着你不能把变量定义“藏”在媒体查询里再全局引用。
- ✅ 正确:
:root { --text-color: #333; } @media (prefers-color-scheme: dark) { :root { --text-color: #e0e0e0; } } - ❌ 错误:
@media (prefers-color-scheme: dark) { :root { --text-color: #e0e0e0; } } body { color: var(--text-color); }(浅色模式下--text-color未声明) - 变量名建议语义化,如
--color-bg、--border-primary,避免带light/dark后缀,方便后续加手动切换逻辑
原生控件和 SVG 不响应变量?这是设计使然
<input>、<select></select>、::-webkit-scrollbar、<svg></svg> 的 fill/stroke 等属性完全不读取 CSS 自定义属性,也不自动适配 prefers-color-scheme。这不是 bug,是规范行为。
立即学习“前端免费学习笔记(深入)”;
- 表单控件需显式设置:
input { background: Canvas; color: CanvasText; }(用系统语义色比硬编码更可靠) - 滚动条要单独写:
::-webkit-scrollbar-thumb { background-color: var(--scroll-thumb, #666); },并确保--scroll-thumb在:root和媒体查询中都定义 - SVG 图标若用内联
fill,必须用 JS 或 class 控制;推荐改用currentColor,让它继承父级color变量 - 别忘了加
color-scheme: light dark到:root或body,这会影响表单控件渲染基调
Tailwind 用户常踩的配置坑
即使写了 darkMode: 'media',Tailwind 也不会自动生效——它只生成对应 CSS 规则,前提是你的 HTML 中实际用了 dark: 类,且基础类不能缺。
- 必须同时写:
bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100(漏掉bg-white,深色下就无背景) -
tailwind.config.js中必须显式写darkMode: 'media',true或'dark'都无效 - JIT 模式下,
content字段必须包含所有含dark:的模板文件路径,否则类名被剔除 - 加
class="dark"到没用——media模式完全绕过 DOM 类控制
最易被忽略的一点:媒体查询只在页面加载时读取一次系统偏好,切换系统主题后已打开的页面不会自动重绘(现代浏览器基本已支持无刷新响应,但 Safari 13.3 以下仍需刷新)。如果你需要“用户点了才切”,darkMode: 'class' 才是正解,media 模式本质是静态适配,不是交互系统。


















