data-theme 必须设在 document.documentElement 上,否则 fixed 元素、伪元素、SVG 填色等无法响应主题;正确做法是在 <head> 内联脚本中立即读取 localStorage 或媒体查询并设置 html[data-theme],同时同步 <meta name="color-scheme">,CSS 中需用 html[data-theme="dark"] 选择器定义变量,并为所有使用变量的属性提供回退值和过渡声明。

data-theme 必须设在 document.documentElement 上
设在 body 或某个容器上,会导致 fixed 元素、伪元素(比如 ::before)、svg 填色等不响应主题——因为 :root 对应的是 html 元素,CSS 变量只有挂载到它身上才能全局生效。
常见错误写法:document.body.setAttribute('data-theme', 'dark'),表面看样式变了,但打开控制台会发现 html 标签上根本没有 data-theme,[data-theme="dark"] 选择器压根没匹配上。
正确初始化方式(放在 <head> 内联脚本中):
const saved = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', saved);
// 同步更新 meta
document.querySelector('meta[name="color-scheme"]').setAttribute('content', saved === 'dark' ? 'dark' : 'light');
CSS 中必须用 html[data-theme="xxx"] 而非 .theme-xxx
用 class 切换(如 body.className = 'dark')极易和 Tailwind、BEM、第三方 UI 库冲突;data-theme 是语义化属性,天然隔离,且服务端渲染时可直接写死在 <html data-theme="light"> 上,首屏不闪。
立即学习“前端免费学习笔记(深入)”;
关键点:
- CSS 选择器必须带
html前缀:html[data-theme="dark"] { --bg: #111; },漏掉html会导致权重不足或作用域错位 - 不要写
:root .theme-dark { --bg: #111; }——这是两个独立选择器,不是嵌套,变量根本不会生效 - 所有可变属性都得显式调用:
border-color: var(--border-color);、fill: var(--icon-fill);,漏掉就等于白定义
transition 和回退值必须提前写死
过渡动画不能靠 JS 动态加 class 或改 style 触发;浏览器只对已声明的 CSS 属性做平滑过渡。同样,变量未定义时若不设回退值,就会显示浏览器默认色(比如黑字配黑底)。
安全写法示例:
body {
background-color: var(--bg, #fff);
color: var(--text, #333);
transition: background-color 0.2s ease, color 0.2s ease;
}
注意:
- 只对重绘类属性加 transition:
color、background-color、border-color、box-shadow✅ - 别给
font-size、padding、width加 transition——它们可能触发重排,卡顿明显 ❌ -
var(--x, #333)的回退值必须是合法 CSS 值,不能是另一个变量名
localStorage 读取时机错一帧,页面就闪一下
DOM 构建完再读 localStorage,浏览器已经按默认主题渲染了一次,JS 再切主题就会有视觉断层。这不是 bug,是渲染流程决定的。
必须把初始化逻辑塞进 <head> 的内联 <script>,且顺序不能乱:
- 先读
localStorage.getItem('theme') - 立刻设
document.documentElement.setAttribute('data-theme', ...) - 再同步设
<meta name="color-scheme"> - 三步必须在
DOMContentLoaded之前完成
放 </body> 底部?太晚。等 DOMContentLoaded?重绘已发生。首屏白屏或闪一下,基本都是这里卡住了。



















