动态主题需在 :root 声明变量、用 setProperty 修改、localStorage 持久化;必须全局定义所有变量,初始化按读取 localStorage→设置 dataset→调用 setProperty 顺序执行,避免闪动。

直接用 :root 声明变量 + setProperty() 修改 + localStorage 持久化,就能跑通动态主题。其他方案要么作用域失效,要么首次渲染闪动,要么跨平台不兼容。
必须在 :root 中定义变量,否则 JS 修改无效
CSS 变量的作用域由声明位置决定。只有写在 :root 里的变量才能被所有元素继承并响应 JS 动态修改。
-
:root { --bg-color: #fff; }✅ 全局可读、可改、可继承 -
.theme-dark { --bg-color: #121212; }❌ 仅该 class 内部生效,外部无法读取,setProperty改了也白改 -
[data-theme="dark"] :root { --bg-color: #121212; }✅ 合法,但需确保这条规则在基础:root之后加载,否则会被覆盖
用 setProperty 更新,别碰 cssText 或 style 字符串
setProperty 是唯一能触发 CSS 变量实时重计算的 API。直接写 style 属性会破坏已有内联样式,且变量不会级联更新。
-
document.documentElement.style.setProperty('--bg-color', '#121212');✅ 安全、精准、可批量 -
document.documentElement.style.cssText = '--bg-color: #121212';❌ 清空全部内联样式,包括其他自定义属性 -
document.documentElement.style['--bg-color'] = '#121212';❌ 不触发更新,浏览器忽略
主题切换必须手动覆盖 prefers-color-scheme
系统偏好只是默认值,用户点“切深色”时,你得立刻记录并应用,不能等 matchMedia 触发才响应。
立即学习“前端免费学习笔记(深入)”;
- 初始化时优先读
localStorage.getItem('ui-theme'),没值再 fallback 到matchMedia('(prefers-color-scheme: dark)').matches - 每次切换主题,同步调用
document.documentElement.dataset.theme = 'dark'和localStorage.setItem('ui-theme', 'dark') - 避免只靠 class 切换(如
html.dark),那样要维护两套完全独立的 CSS,违背变量设计初衷
变量粒度要分层,但所有分支必须定义全部变量
不是“只改深色需要的变量”,而是每套主题都得显式声明所有用到的变量。缺一个,下游 var(--xxx) 就会回退到 inherit 或 initial,导致样式断裂。
- 推荐分语义层:颜色层(
--color-primary)、间距层(--space-md)、圆角层(--radius-sm) - 深色主题里也要写
--space-md: 8px;,哪怕和亮色一样——否则组件里引用它时可能出错 - 避免在组件级选择器里覆盖变量(如
.card { --color-bg: #f9fafb; }),这会让主题逻辑分散、不可控
最容易被忽略的是初始化顺序:先读 localStorage,再 set dataset,最后 setProperty。漏掉任一环,首屏就会闪一下亮色再变暗,或者刷新后回到系统默认而非用户上次选择。


















