:root + @media (prefers-color-scheme: dark) 可实现无JS自动响应系统主题,需配合 html { color-scheme: light dark; } 和一致变量名;手动切换须用 localStorage + data-theme 类控制,并避免与媒体查询冲突。

直接用 :root + @media (prefers-color-scheme: dark) 最省事
不需要 JS 就能自动响应系统设置,适合“默认跟随系统、不提供手动切换”的场景。浏览器一读到这个媒体查询,就会在暗黑系统下自动覆盖 :root 里的变量值。
- 必须把暗色变量写在媒体查询内部,不能只靠外部类名(否则无法自动触发)
-
html { color-scheme: light dark; }要加上,否则滚动条、表单控件等原生元素不会变色 - 变量名要完全一致,比如
--bg-color在明暗两套里都得叫这个名字,否则var(--bg-color)会 fallback 到初始值甚至失效
:root {
--bg-color: #ffffff;
--text-color: #333333;
--border-color: #e0e0e0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #1a1a1a;
--text-color: #f0f0f0;
--border-color: #333333;
}
}
html {
color-scheme: light dark;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
想让用户手动切换?必须用 localStorage + document.documentElement.classList
仅靠 prefers-color-scheme 是被动响应,用户点了“切暗色”按钮后刷新就回退——因为媒体查询不存状态。得靠 JS 记住选择,并通过类名触发变量重定义。
- 推荐用
data-theme="dark"或class="dark",别直接改style属性,否则和媒体查询冲突 - 初始化时优先读
localStorage.getItem('theme'),没值再 fallback 到matchMedia检测结果 - 监听
matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ...),但只在用户没手动选过时才响应,避免覆盖用户偏好
const saved = localStorage.getItem('theme');
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const themeToUse = saved ?? (systemDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', themeToUse);
// 切换按钮
document.getElementById('theme-toggle').addEventListener('click', () => {
const cur = document.documentElement.getAttribute('data-theme');
const next = cur === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
[data-theme="dark"] 和 .dark 写法有啥区别?
本质一样,都是靠 CSS 选择器作用于 :root 或其他元素来覆盖变量。区别只在语义和可维护性:
-
[data-theme="dark"]更明确表示“这是主题控制属性”,避免和普通样式类(如.header-dark)混淆 -
.dark更短,Tailwind 等框架默认认这个类名,生态兼容性好 - 不要混用:比如一边用
.dark控制变量,一边又用@media (prefers-color-scheme: dark)写同样变量——容易互相覆盖,调试时抓狂
:root {
--bg-color: #fff;
}
[data-theme="dark"] {
--bg-color: #121212;
}
/ ✅ 安全:媒体查询只作为兜底 /
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--bg-color: #121212;
}
}
过渡动画卡顿?检查 transition 是否写对了
CSS 变量本身不触发重排,但如果你只给 background-color 加了 transition,而实际变的是 --bg-color,那动画根本不会动——因为变量变化不直接触发 transition,得靠被 var() 引用的属性来驱动。
立即学习“前端免费学习笔记(深入)”;
- 必须把
transition写在使用var(--xxx)的元素上,而不是:root - 多个变量同时变时,建议统一用
transition: all 0.3s ease;,避免漏写某个属性 - 如果用了
transform或opacity动画,它们天然支持硬件加速;但颜色类属性(background-color,color)只能靠主线程计算,大量元素一起动会掉帧
body {
background-color: var(--bg-color);
color: var(--text-color);
/* ✅ 正确:transition 写在这里 */
transition: background-color 0.3s ease, color 0.3s ease;
}真正麻烦的不是怎么写,而是变量命名和作用域管理——比如一个组件里同时用了 --card-bg 和 --bg-color,切主题时漏掉其中一个,视觉就错位。这种问题不会报错,只能靠人工核对或写 CSS 自检脚本。









