最轻量可靠的暗色模式方案是:root声明变量+@media (prefers-color-scheme: dark)兜底+.dark类手动切换;所有变量须在:root中完整定义默认值,媒体查询须置于其后,js仅通过classlist.toggle('dark')切换主题,并同步持久化至localstorage。

:root` 声明变量 + `@media (prefers-color-scheme: dark)` 兜底 + `.dark` 类手动切换,三者不冲突、不覆盖、各司其职——这是目前最轻量也最可靠的方案。
所有变量必须写在 :root 里,否则组件读不到
CSS 自定义属性靠继承传播,只有 `:root`(即 `html` 元素)能确保任意元素通过 `var(--bg-color)` 拿到值。写在 `.dark`、`body` 或组件类里,变量只对该选择器及其后代生效。
-
.dark { --bg-color: #121212; }→ `input` 若没被包裹进 `.dark`,就 fallback 到透明或浏览器默认色 -
body { --text-color: #eee; }→ `var(--text-color)` 在 `html` 上求值时根本找不到定义 - Shadow DOM 内部不会继承外部 `
:root` 变量,得在 `:host` 或 `::slotted` 里手动传入
@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) { } }` 是无效结构
JS 切换只操作 `class`,别碰 `style.setProperty()`
核心是只操作 class,后者会废掉整套媒体查询逻辑,且维护爆炸。
- ✅ 正确做法:
document.documentElement.classList.toggle('dark') - ❌ 绝对避免:
document.documentElement.style.setProperty('--bg-color', '#121212') - 初始化必须早于首屏渲染:在 `
<script></script>` 标签内直接执行,别等 `DOMContentLoaded` - 同步持久化:
localStorage.setItem('theme', newTheme)和 `classList` 必须一起做,缺一不可 - 检查 class 名是否匹配 CSS 中的 `
:root.dark`:JS 切 `dark` 但 CSS 写 `.dark-mode` 就完全不生效
变量命名和使用要防 fallback 失效
写 `color: var(--text-primary, #333)` 看似稳妥,但一旦变量名拼错(比如 `--text-prmiary`),浏览器直接回退到 fallback,样式就崩了,还很难 debug。
- 所有变量名统一加语义前缀,比如 `
--theme-bg-primary`、`--theme-text-secondary`,避免和第三方库(如 Tailwind 的 `--`)冲突 - 命名必须带单位(如 `
--spacing-md: 1rem`),禁用 `!important`——它对 `var()` 无效 - 颜色必须配对更新:设了 `
--bg-color: #000`,却忘了 `--text-color: #fff`,对比度不足会导致可访问性失败 - 务必加 `
color-scheme: light dark` 告诉浏览器你支持双模式,否则 `<input>`、`<select></select>` 还是白底黑字
:root` 变量必须在 CSS 解析完成前就存在,JS 设置 class 也得赶在首屏渲染之前;稍有延迟,就会闪屏。很多人卡在这一步,不是逻辑错,是执行顺序没压准。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











