先分享一个几乎不会出错的实践要点:主题变量必须统一放置在 :root 中。为什么这样坚持?因为 :root 本身就是文档的继承根节点,等价于 ,但优先级更高、语义更精准。将变量挂载在此处后,var(--primary-color) 才能在包括 ::before、svg、表单伪类等任意层级正确取值。如果变量写在 .dark 或某个组件选择器内,作用域会立刻受限,JS 调用 document.documentElement.style.setProperty() 也会完全失效。
实际上,常见的误用远不止这些。比如有人用 html { --color: red; },这在非标准环境下可能失效;有人写成 .theme-dark { --color: red; },一旦作用域被限制就会出问题;还有人漏掉双短横线,比如只写 primary-color,浏览器会直接忽略整条声明。所以,务必在 :root 中按照标准格式声明 CSS 变量。
为什么 data-theme 属性比 class 更可靠
使用 [data-theme="dark"] :root 来覆盖变量,CSS 权重固定为 110,远高于普通 class 的 10,不会和业务类名(例如 .dark-mode-toggle)产生冲突。此外,Safari 旧版对 @media (prefers-color-scheme) .dark :root 的支持不够稳定,但 [data-theme] 全系兼容,无需担心兼容性问题。
切换主题时不能只更新 dataset.theme,还必须同步保存到 localStorage。否则一刷新页面,主题就会回退:
document.documentElement.dataset.theme = 'dark';
localStorage.setItem('ui-theme', 'dark');
更关键的是:初始值必须在 的内联脚本中预先设定。如果浏览器先按无主题渲染,再用 JS 补切,必然会导致闪屏:
JS 修改变量的正确方式:设属性,而不是改 style
很多人调用 document.documentElement.style.setProperty('--bg-color', '#121212') 却没有效果,原因在于这套逻辑只适用于“变量已预先在 :root 中定义”并且样式规则里正在使用 var(--bg-color) 的场景。但实际上,真正触发主题切换的是 data-theme 属性变化后,CSS 选择器重新匹配并覆盖变量。
因此核心动作只有这一个:
- ✅ 正确:
document.documentElement.setAttribute('data-theme', 'dark') - ❌ 错误:
document.documentElement.style.cssText = '--bg-color: #121212'(会清空所有内联样式) - ❌ 错误:
document.documentElement.style.setProperty('--bg-color', '#121212')单独调用(不会触发 CSS 选择器重匹配)
如果需要批量更新,可以封装成如下函数:
function setTheme(vars) {
Object.entries(vars).forEach(([k, v]) =>
document.documentElement.style.setProperty(k, v)
);
}
但前提是:这些变量已经在 :root 和 [data-theme="dark"] :root 中完整声明,否则 var(--x) 会 fallback 到 transparent 或者继承值,页面会瞬间“失色”。
最容易遗漏 var() 替换的几个位置
主题色不仅存在于按钮或标题上。下面这些地方如果硬编码颜色值,切换就会失败:
background-color、color、border-color、box-shadow、outline-color中的#3b82f6等写死值::before和::after伪元素里的background和color(尤其是图标装饰)svg标签内的fill和stroke(必须显式写成fill: var(--primary-color))- 表单状态样式:
input:focus、button:active、select:disabled - 第三方 UI 库(如 Ant Design)内部 class —— 如果没有开启 CSS 变量支持,需要手动 patch 或者更换库
过渡动画不能直接写在变量名上(transition: --primary-color 无效),要作用于具体属性:background-color: var(--bg-color); transition: background-color 0.3s ease;。
最后,真正容易被忽视的是,所有尺寸、圆角、间距等变量也必须在初始 CSS 中定义。否则主题切换时 layout 会发生抖动,用户感知会非常明显。
