必须将变量定义在 :root 中,因为 css 自定义属性依赖继承,只有 :root 能确保全局可访问;写在 .dark 中会导致原生控件无法读取变量,且媒体查询无法覆盖;data-theme="dark" 语义更清晰、避免 class 冲突、支持精准覆盖。

为什么必须把变量定义在 :root 而不是 .dark 里
因为 CSS 自定义属性靠继承传播,而只有 :root(即 元素)能确保任意后代元素都能读取到 var(--bg)。写在 .dark 选择器里,变量只在该类匹配的元素及其子树生效;<input>、<button></button> 这类原生控件若没被包裹,就会 fallback 到透明或浏览器默认值。
更关键的是:媒体查询 @media (prefers-color-scheme: dark) 只能覆盖 :root 下声明的变量——它不认你自定义的类名,也不进 Shadow DOM。所以一旦变量不在 :root,系统自动切换就直接失效。
data-theme="dark" 和 class="dark" 选哪个
优先用 data-theme="dark"。它语义更清晰,避免和第三方库(如 Tailwind 的 dark: 前缀、Ant Design 的 .ant-dark)产生 class 名冲突。更重要的是,它能绕过 dark: 前缀的硬编码限制——Tailwind 的 dark:bg-gray-800 完全不响应 CSS 变量变化,但 [data-theme="dark"] .btn 可以精准覆盖。
使用时注意三点:
-
data-theme必须设在标签上,否则子元素无法继承 - 切换时要同步操作:
document.documentElement.setAttribute('data-theme', 'dark')+localStorage.setItem('theme', 'dark') - 媒体查询里加
:root:not([data-theme="light"]) { ... },防止用户手动选了 light 后又被系统暗色偏好覆盖
var(--color-bg) 不生效的常见原因
不是语法错,是层叠或作用域问题。典型表现有:
- 同时写了
text-gray-700 dark:text-gray-200和text-[var(--text-color)],前者优先级更高,直接覆盖变量值 - 变量名不一致:CSS 里定义的是
--bg-primary,但 HTML 中用了var(--background),结果 fallback 到初始值甚至透明 - Tailwind 构建时 PurgeCSS 把
bg-[var(--color-bg)]当作动态值剔除了,需要在tailwind.config.js的safelist加正则/^bg-\[.*\]$/ - 变量值含空格:
var( --color-bg )会编译失败,必须写成var(--color-bg)
原生控件颜色不随主题变?检查 color-scheme
color-scheme: light dark 是唯一能让 <input>、<select></select>、滚动条、canvas 边框等原生 UI 元素自动适配的 CSS 属性。它不控制你的业务样式,只告诉浏览器“我支持双模式”。漏掉这行,哪怕所有变量都对了,表单控件在暗色系统下仍显示白底黑字+亮色边框。
必须写在 或 上,且推荐搭配 [data-theme="dark"] 使用:
html {
color-scheme: light dark;
}
[data-theme="dark"] {
color-scheme: dark;
}
这样既兜底系统偏好,又确保用户手动切换后原生控件也能响应——这点最容易被忽略,尤其在移动端调试时。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











