必须把主题变量写在:root里,因为:root是文档继承根,等价于<html>但优先级更高、语义更准,确保var(--primary-color)在任意层级(包括::before、svg、表单伪态)正确取值;写在其他选择器中会导致作用域受限、JS修改无效或页面“失色”。
:root 里因为 :root 是整个文档的继承根,等价于 <html> 但优先级更高、语义更准。变量挂在这里,var(--primary-color) 才能在任意层级(包括 ::before、svg、表单伪态)正确取值。写在 .dark 或组件选择器里,变量只在局部生效,JS 调用 document.documentElement.style.setProperty() 完全无效。
常见错误包括:html { --color: red; }(非标准,部分环境失效)、.theme-dark { --color: red; }(作用域受限)、漏掉双短横线(primary-color → 浏览器直接忽略整条声明)。
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');
更重要的是:初始值必须在 <head> 内联脚本中预设,否则浏览器先按无主题渲染,再 JS 补切,必然闪屏:
<script>document.documentElement.dataset.theme = localStorage.getItem('ui-theme') || 'light';</script>
很多人调用 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
过渡动画不能写在变量名上(transition: --primary-color 无效),要作用于具体属性:background-color: var(--bg-color); transition: background-color 0.3s ease;。
真正容易被忽略的,是所有尺寸、圆角、间距变量也得在初始 CSS 中定义。否则主题切换时 layout 抖动,用户感知明显。