必须将CSS变量声明在:root下,用:root.dark覆盖值,配合localStorage同步存储、脚本提前执行及统一前缀命名,才能实现稳定可靠的深色模式切换。

必须把变量声明在 :root,否则切换无效
很多人写 .dark .card { --bg-color: #121212; },结果 JS 切了 class,页面颜色纹丝不动。原因很简单:CSS 变量作用域只限于声明它的选择器及其后代,.dark 并不是根元素,子组件读不到这个变量——继承链断了。
正确做法只有一条::root 声明所有变量,再用 :root.dark 覆盖值:
:root {
--bg-primary: #ffffff;
--text-primary: #333333;
}
:root.dark {
--bg-primary: #121212;
--text-primary: #e0e0e0;
}
-
:root.dark是普通类名,不是伪类,JS 用document.documentElement.classList.toggle('dark')控制即可 - 别用
@media (prefers-color-scheme: dark)替代——它响应不了用户手动点击,也不支持 SSR 初始渲染 - 所有主题相关变量(颜色、间距、阴影)都走这套机制,避免重复写媒体查询或直接操作
style
初始化必须早于 DOM 渲染,否则首屏闪白
用户点一下按钮切到深色,刷新后又变回浅色;或者刚打开页面时明显闪一下(白底→黑底)——90% 是因为初始化逻辑太晚。
关键动作必须在 DOM 构建前完成:
立即学习“前端免费学习笔记(深入)”;
const root = document.documentElement;
const savedTheme = localStorage.getItem('theme') || 'light';
if (savedTheme === 'dark') {
root.classList.add('dark');
} else {
root.classList.remove('dark');
}
-
<script>放<head>里,且不能等DOMContentLoaded;推荐用document.write或内联脚本 - 切换按钮点击后,除了
toggleclass,必须同步执行localStorage.setItem('theme', newTheme) - 千万别用
root.style.setProperty('--bg-primary', '#121212')——这会覆盖整套:root.dark声明,后续维护成本爆炸
组件内用 var() 要防拼错和 fallback 失效
color: var(--text-primary, #333) 看似安全,但一旦变量名写成 --text-prmiary(少个 a),浏览器直接回退到 #333,样式崩了却查不出源头。
- 所有变量名强制加统一前缀,比如
--theme-bg-primary、--theme-text-secondary - 避免和第三方库冲突(如 Tailwind 的
--tw-前缀)或未来原生 CSS 属性重名 - 不要依赖 fallback 值兜底,开发期就该用 DevTools 的
Computed面板确认变量是否被正确继承和解析
Angular 中别混用 SCSS 变量和 CSS 变量
有人在 light.theme.scss 里写 $bg-color: #fff,再用 JS 动态改这个 SCSS 变量——根本没用。SCSS 是编译时处理,运行时 JS 拿不到、也改不了。
- SCSS 变量只适合静态主题生成(比如构建时输出
light.css和dark.css),不适合运行时切换 - 如果项目已用 NG-ZORRO,优先走
provideNzConfig+NzConfigService.set('theme', {...}),它底层就是注入 CSS 变量 - 自定义组件中要用主题色,直接写
background-color: var(--theme-bg-primary);,别试图从 TS 里传 SCSS 变量过去
:root 类名同步、localStorage 读写时机精准、变量命名不撞车——这些细节漏一个,主题就“看起来能切,实际总出岔”。


















