: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="tuc-19bc10f7-27166c-0 dark tuc-19bc10f7-27166c-0",别直接改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 自检脚本。