必须将所有主题变量定义在 :root 中,因为 css 自定义属性依赖继承,只有 :root 能确保全局可访问;其他选择器范围受限,shadow dom 不继承外部变量;@media 仅作兜底,需用 .dark 类配合 js 控制 class 切换,并在 dom 加载前读取 localstorage 初始化。

必须把所有主题变量定义在 :root 里,否则切换必然失效——这不是可选项,是 CSS 变量作用域的硬性限制。
为什么只能在 :root 声明变量
CSS 自定义属性靠继承传播,而只有 :root(即 元素)能确保任意元素通过 var(--bg-color) 正确读取。写在 .dark、body 或组件类里,变量只在该选择器匹配的子树生效:
-
.theme-dark { --bg-color: #121212; }→button或input若未被包裹,就 fallback 到透明或浏览器默认值 -
body { --text-color: #eee; }→var(--text-color)在上求值时根本找不到定义 - Shadow DOM 内部不会继承外部
:root变量,需在:host或::slotted里手动传入
@media (prefers-color-scheme: dark) 只负责赋值,不替代用户点击
媒体查询只在页面加载和系统设置变更时触发,它无法响应用户主动切换。比如系统设为浅色,但用户想在你站用深色——这时 @media 完全无响应:
- 它不支持
localStorage持久化:刷新后状态丢失 - 旧版 Safari(iOS 13 以下)对
prefers-color-scheme支持不稳定 - 正确做法是把它当作“默认兜底”,再用
.dark类覆盖同一组变量,靠 JS 控制document.documentElement.classList.toggle('dark')
JS 切换时别碰 style.setProperty()
调用 document.documentElement.style.setProperty('--bg-color', '#121212') 看似简单,实际会破坏整套机制:
- 覆盖
@media和.dark的声明逻辑,导致系统切换失效 - 大量变量逐个设置会卡 UI 线程,尤其在低端设备上明显
- 维护成本爆炸:新增一个颜色就得同步改 JS 和 CSS 两处
- 真正轻量的做法是只操作 class:
document.documentElement.classList.add('dark'),所有变量值由 CSS 规则自动重算
初始化必须早于首屏渲染,且要联动 localStorage
页面闪屏(白底→黑底)90% 是因为 class 没在 DOM 加载完成前设置。初始化逻辑必须三步到位:
- 读取
localStorage.getItem('theme'),而非依赖matchMedia初始值 - 在
<script></script>标签内直接操作document.documentElement.classList,不要等DOMContentLoaded - 切换按钮点击后,同步执行
classList.toggle('dark')和localStorage.setItem('theme', newTheme)
漏掉任一环,用户刷新后主题就回退,或者原生控件(<input>、<select></select>)颜色不一致——这比变量名拼错更难排查,因为视觉异常但控制台没报错。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











