不能在 .dark 类里直接声明 CSS 变量,因为 CSS 自定义属性依赖继承传播,只有 :root 能确保全局可访问;写在 .dark 或 body 中会导致未包裹元素(如独立 input)无法读取变量而样式崩坏。

为什么不能在 .dark 类里直接声明 CSS 变量?
因为 CSS 自定义属性靠继承传播,而只有 :root(即 <html> 元素)能确保任意元素、伪元素、原生控件(如 input、button)、Shadow DOM 外部都能读到变量值。写在 .dark 或 body 里,变量只对该选择器及其后代生效——比如一个没被包裹的 input 就会 fallback 到透明或浏览器默认色,样式直接崩。
正确结构必须是:
:root {
--theme-bg: #ffffff;
--theme-text: #333333;
}
.dark :root {
--theme-bg: #121212;
--theme-text: #e0e0e0;
}
注意:.dark :root 是合法选择器,依赖 HTML 根节点有 class="dark";它不是伪类,JS 用 document.documentElement.classList.toggle('dark') 即可切换。
@media (prefers-color-scheme: dark) 必须怎么写才生效?
它只负责系统偏好兜底,不是万能开关。写错位置或漏变量,一刷新就白屏或颜色错乱。
立即学习“前端免费学习笔记(深入)”;
- 必须放在
:root默认声明之后,否则旧版 Safari 直接忽略 - 所有变量必须先在
:root中完整声明默认值,否则系统设为no-preference时var(--border-color)会 fallback 成transparent - 不能只覆盖部分变量:漏了
--shadow-sm,用了box-shadow: var(--shadow-sm)的按钮就没了阴影 - 禁止嵌套:
@media (max-width: 768px) { @media (prefers-color-scheme: dark) { ... } }是无效结构
正确写法:
:root {
--theme-border: #e0e0e0;
--theme-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--theme-border: #333333;
--theme-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
}
Sass 怎么批量生成明/暗两套变量,又不手写重复?
手动维护两套变量极易出错,尤其颜色层级多时。Sass 的 @each 和 @mixin 能把映射抽象成 map,一次生成所有变量,且保证顺序和完整性。
推荐做法:
@function theme-map($light, $dark) {
@return (light: $light, dark: $dark);
}
$colors: (
bg: theme-map(#ffffff, #121212),
text: theme-map(#333333, #e0e0e0),
border: theme-map(#e0e0e0, #333333)
);
:root {
@each $name, $map in $colors {
--theme-#{$name}: map-get($map, light);
}
}
.dark :root {
@each $name, $map in $colors {
--theme-#{$name}: map-get($map, dark);
}
}
关键点:
- 所有变量名统一加前缀(如
--theme-bg),避免和第三方库(如 Tailwind 的--tw-)冲突 - 别用
lighten()或darken()推导深色值——它们只调 HSL 的 L 值,对纯黑/白无效,且对比度不可控 - 颜色值必须显式定义,不能依赖函数“自动变暗”;WCAG 对比度需人工校验
JS 初始化和切换时最容易踩的三个坑
90% 的闪屏、状态丢失、切换无效,都来自这三步没做对。
- 初始化必须早于首屏渲染:把脚本放在
<head>里,用document.documentElement.classList.add('dark')直接执行,别等DOMContentLoaded - localStorage 必须同步操作:切换时既要
classList.toggle('dark'),也要localStorage.setItem('theme', newTheme),缺一不可 - 绝对避免
style.setProperty():比如document.documentElement.style.setProperty('--theme-bg', '#121212')会覆盖整套:root.dark声明,媒体查询失效,维护爆炸
初始化示例:
const root = document.documentElement;
const savedTheme = localStorage.getItem('theme') || 'light';
if (savedTheme === 'dark') {
root.classList.add('dark');
} else {
root.classList.remove('dark');
}
切换按钮事件里,只做两件事:更新 class、存 localStorage —— 其他逻辑全交给 CSS 变量和媒体查询去响应。
真正麻烦的不是写代码,而是变量命名一致性、fallback 安全性、以及 Shadow DOM 或第三方组件里变量是否被正确继承。这些地方一漏,调试成本远高于实现本身。


















