css自定义属性必须声明在:root(即document.documentelement)上,否则作用域受限;主题切换推荐用data-theme属性而非class;localstorage应存语义化主题名而非颜色值;var()仅限css使用,js需用getcomputedstyle获取。

document.documentElement 是唯一合法的变量宿主
CSS 自定义属性(常被误称为“HTML 变量”)必须挂载在 :root 对应的元素上,也就是 document.documentElement。写在 、<div> 或任意 class 里,会导致作用域受限、继承断裂或切换失效。
<p>常见错误现象:<code>document.body.style.setProperty('--bg', '#222') 后,.card { background-color: var(--bg); } 没反应——因为 --bg 没在 :root 声明,后代无法继承。
- 所有主题变量必须统一声明在
:root块中,哪怕只用 JS 动态赋值 - 不要给
或组件加style="--x: y",它不构成全局作用域 - SSR 或初始渲染时,确保 HTML 中已存在
或至少有:rootCSS 规则,否则getComputedStyle读不到默认值
data-theme 属性比 class 切换更干净
用 document.documentElement.setAttribute('data-theme', 'dark') 触发主题变更,比操作 classList 更可控、更少副作用。
原因在于:CSS 层叠优先级明确,[data-theme="dark"] :root 规则天然高于默认 :root;而 .theme-dark 类容易和框架类(如 Tailwind 的 dark:)、第三方 UI 库冲突,且需大量嵌套选择器才能覆盖变量。
- 对应 CSS 必须写成
[data-theme="dark"] :root { --bg: #121212; },不是[data-theme="dark"] { --bg: ...; } - 如果同时用了
style.setProperty()和data-theme,记得先removeAttribute('data-theme'),否则两者规则会打架 - 服务端渲染时,可直接在
中输出初始状态,避免 FOUC
localStorage 存的是语义标识,不是颜色值
存 "dark" 或 "blue" 这类主题名,而不是 "#1a1a1a" 这种具体颜色值。前者可扩展、易维护、防拼写错误;后者一旦改设计就得全量搜索替换,且无法支持多变量联动(比如深色模式不只是背景变黑,字体、边框、阴影都要同步)。
- 保存时:
localStorage.setItem('preferred-theme', 'dark') - 加载时:
const theme = localStorage.getItem('preferred-theme') || 'light'; document.documentElement.setAttribute('data-theme', theme) - 务必在 CSS 加载完成、DOM 渲染前执行这步——放在
<script></script>标签内,不要等DOMContentLoaded,否则用户会看到闪屏
var() 不是 JS 变量,不能直接读取或计算
var(--bg) 是纯 CSS 函数,运行在样式计算阶段,JS 无法“读取”它的实时计算结果。你调用 getComputedStyle(document.documentElement).getPropertyValue('--bg'),拿到的只是当前 style 或匹配规则中显式设置的原始值(如 "#1a1a1a"),不是经过层叠后最终生效的值。
- 想获取某元素最终使用的背景色?得查那个元素本身:
getComputedStyle(el).backgroundColor - 不要在 JS 里写
const color = var(--bg)—— 语法错误,var()只能在 CSS 中用 - 过渡动画不能写
transition: --bg 0.3s,必须指定具体属性,例如transition: background-color 0.3s
真正难的不是设变量,而是确认每个 var(--x) 都被真实用到了样式里,并且没被更高优先级的选择器意外遮蔽。检查时打开 DevTools 的 Computed 面板,点开某个元素,搜 background-color,看来源是不是你预期的那条 :root 规则。











