document.documentelement 是唯一能触发全局样式重计算的节点,所有主题切换必须操作它;需用 dataset.theme 设置属性、html[data-theme] 选择器定义变量、监听 prefers-color-scheme 变更并同步更新 meta[name="color-scheme"]。

document.documentElement 是唯一能触发全局样式重计算的节点
所有主题切换逻辑必须操作 document.documentElement,而不是 body 或其他元素。因为只有 HTML 根节点能影响 :root 变量、::selection、滚动条样式、textarea 边框、SVG fill 继承,以及第三方库(如 Tailwind、Bootstrap)注入的主题类。挂错位置会导致部分区域颜色不一致,比如按钮变暗但输入框边框仍是浅灰。
常见错误是写 document.body.setAttribute('data-theme', 'dark'),结果 html[data-theme="dark"] 选择器完全不匹配,CSS 变量压根没生效。
-
document.documentElement.dataset.theme = 'dark'是正确写法;设为空字符串可清空属性 - 不要用
classList.add('dark')挂在html上——语义不清,且易与已有 class 冲突(如 SSR 注入的ssr-dark) - 初始化时若
localStorage.getItem('theme')返回null,不能直接赋给dataset.theme,否则变成data-theme="null",CSS 不会匹配任何规则
data-theme 属性必须配合 html 前缀选择器才能生效
CSS 中写 [data-theme="dark"] { --bg: #1a1a1a; } 是无效的,它没有指定作用域,浏览器会按默认权重应用,极易被子元素内联样式或第三方 CSS 覆盖。必须显式带上 html 前缀,确保选择器权重足够、作用范围准确。
同时,:root 必须预先定义默认变量值,否则在未匹配到 html[data-theme] 规则时,var(--bg) 会退化为 initial,导致背景透明、文字不可见等闪屏问题。
- 正确写法:
html[data-theme="light"] { --bg: #fff; --text: #333; }和html[data-theme="dark"] { --bg: #121212; --text: #e0e0e0; } - 禁止只写 dark 版本指望 fallback——
:root里必须有完整亮色变量集 - 所有组件样式必须用
background: var(--bg),禁用硬编码色值(如#121212),否则无法响应主题切换
监听系统偏好变更必须手动绑定 change 事件
@media (prefers-color-scheme: dark) 本身是静态的,页面加载后不会自动响应系统级主题切换。不加监听,用户在 macOS 设置里切了深色模式,网页毫无反应,要刷新才生效。
现代浏览器(Chrome 87+、Firefox 96+、Safari 14+)支持 change 事件,但必须显式调用 addEventListener,且回调中需同步更新 DOM 和存储。
- 监听代码必须写成:
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { document.documentElement.dataset.theme = e.matches ? 'dark' : 'light'; localStorage.setItem('theme', e.matches ? 'dark' : 'light'); }) - 该监听应在 DOM 加载完成后立即注册,不能等到
window.onload,否则可能错过首次系统变更事件 - 注意:该 media query 的初始值(
e.matches)不能直接用于初始化,因为用户可能已手动覆盖过偏好,应以localStorage为第一优先级
meta[name="color-scheme"] 必须随主题动态更新
仅改页面样式不够,安卓 Chrome 和 Safari 会读取 <meta name="color-scheme" content="light dark"> 来决定地址栏、表单控件、滚动条 thumb 等系统 UI 的颜色。如果 JS 切了主题但忘了更新这个 meta,就会出现“页面已暗,地址栏还是白”的割裂感。
这个 meta 不会自动同步,每次主题变更都得手动改。
- 设置方式:
document.querySelector('meta[name="color-scheme"]').content = 'light dark'(注意:始终写两个值,浏览器自行判断) - 首次加载时也要从
localStorage读一次主题,再设 meta,否则首屏不匹配 - 旧版浏览器忽略该 meta,不报错也不影响功能,但新环境里漏掉它,体验就断层
最容易被忽略的是初始化时机和变量兜底:DOMContentLoaded 阶段必须完成 dataset.theme 设置 + meta 更新 + CSS 变量定义,三者缺一都会导致闪屏;而所有变量必须在 html[data-theme] 规则外先有默认值,否则 var(--x) 在未命中规则时直接失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











