真正可维护的深色模式方案是将其作为「主题维度」与断点同等纳入Mixin统一调度,避免嵌套@media导致Safari等浏览器忽略,强制顶层扁平声明并完整重设所有CSS变量。

直接用 @media (prefers-color-scheme: dark) 写死在样式里,短期能跑,长期必翻车——变量不生效、Safari 不认、和响应式断点嵌套冲突、JS 主题切换后样式错乱。真正可维护的做法是把深色模式当作一个「主题维度」,和断点一样纳入 Mixin 统一调度。
为什么不能嵌套 @media (prefers-color-scheme: dark) 在其他媒体查询里
旧版 Safari(≤14.1)和部分安卓 WebView 会直接忽略嵌套的 @media (prefers-color-scheme: dark)。比如写成 @media (min-width: 768px) { @media (prefers-color-scheme: dark) { ... } },深色规则根本不会被解析。
- 必须让
@media (prefers-color-scheme: dark)保持顶层、扁平、无包裹 - 如果同时要响应设备宽度和系统主题,得用逻辑组合:比如
@media (min-width: 768px) and (prefers-color-scheme: dark),而不是嵌套 - 所有主题相关变量(如
--bg、--text)必须在该顶层媒体查询内部完整重声明,不能只改一部分
theme-mq() Mixin:支持 light/dark 的双模式封装
这个 Mixin 不是替代 @media,而是把它和主题语义绑定,避免手写重复条件、漏 fallback、错顺序。
@use "sass:map";
$themes: (
light: (
bg: #ffffff,
text: #1e293b,
border: #e2e8f0
),
dark: (
bg: #0f172a,
text: #f1f5f9,
border: #334155
)
);
@mixin theme-mq($mode) {
@if map.has-key($themes, $mode) {
@if $mode == light {
// 默认即 light,不加媒体查询,但需显式声明变量
:root {
--bg: #{map-get(map-get($themes, light), bg)};
--text: #{map-get(map-get($themes, light), text)};
--border: #{map-get(map-get($themes, light), border)};
}
} @else if $mode == dark {
@media (prefers-color-scheme: dark) {
:root {
--bg: #{map-get(map-get($themes, dark), bg)};
--text: #{map-get(map-get($themes, dark), text)};
--border: #{map-get(map-get($themes, dark), border)};
}
}
}
}
}
- 调用方式:
@include theme-mq(light);和@include theme-mq(dark); - 它强制你为每个主题提供完整变量集,避免漏设
--border导致组件边框消失 - 默认
light不走媒体查询,符合浏览器渲染逻辑;dark则严格走原生检测 - 不依赖
!important或 class 切换,和 JS 运行时主题互不干扰
和断点 Mixin(如 mq())共用时的顺序陷阱
如果你已有 @mixin mq($breakpoint) 处理响应式,千万别写成 @include mq(md) { @include theme-mq(dark) { ... } }——这又掉进嵌套坑里了。
立即学习“前端免费学习笔记(深入)”;
- 正确做法是「先主题,再断点」或「合并条件」:
@media (min-width: 768px) and (prefers-color-scheme: dark) { ... } - 或者拆成两个独立块:
@include theme-mq(dark) { @include mq(md) { ... } },前提是mq()输出的是纯 CSS 规则,不是嵌套媒体查询 - 检查编译后 CSS:确保最终生成的是单层
@media,而不是@media { @media { ... } } - DevTools 中模拟主题切换后,若某条规则被划掉但样式没变,大概率是 selector 优先级被外层断点类或 inline style 压制了
最易被忽略的一点:CSS 变量的 fallback 不是自动的。如果 :root 里没先定义 --bg,而只在 @media (prefers-color-scheme: dark) 里设,那浅色模式下 var(--bg) 就是无效值——必须在顶层 :root 显式初始化所有主题变量,哪怕只是占位。


















