用 @media (prefers-color-scheme: dark) 可原生响应系统深色模式,页面加载时读取一次偏好;需将深色规则置于默认样式之后、避免嵌套、优先使用 Canvas/CanvasText 等语义化系统色,并确保对比度符合可访问性标准。

怎么用 @media (prefers-color-scheme) 检测系统深色模式
浏览器原生支持,不需要 JS,直接靠 CSS 媒体查询就能响应系统设置。关键是 @media (prefers-color-scheme: dark) 这个规则——它只在用户开启系统深色模式时生效。
注意:这个媒体查询不是“实时监听”,而是页面加载时读取一次系统偏好。切换系统主题后,已打开的页面不会自动重绘(除非刷新或手动触发),这点常被忽略。
-
prefers-color-scheme有三个可能值:light、dark、no-preference(极少触发,可不处理) - 必须写在 CSS 文件里或
<style>标签中,不能用document.styleSheets动态插入后生效(部分浏览器不识别) - 不要嵌套在其他媒体查询里,比如
@media (max-width: 768px) and (prefers-color-scheme: dark)虽语法合法,但可读性差、调试困难,建议拆开
如何安全地覆盖默认颜色而不破坏可访问性
很多人一上来就全局替换 background 和 color,结果文字对比度暴跌,尤其在深色下用浅灰字配深灰背——看着“暗”了,实则不符合 WCAG AA 标准。
真正稳妥的做法是:优先继承系统语义化颜色,再局部微调。比如用 Canvas、CanvasText 这类系统色关键词,它们会随系统主题自动变化。
立即学习“前端免费学习笔记(深入)”;
- 避免硬写
#121212或#ffffff,改用color-scheme: light dark告诉浏览器你的样式支持双模式(影响表单控件渲染) - 深色模式下,
background-color: Canvas比#1e1e1e更可靠;color: CanvasText比#e0e0e0更适配高对比度模式 - 如果必须用自定义色,请用
lab()或lch()定义(现代浏览器支持),它们能保持亮度层级,比rgb()更易控制对比度
@media 规则写在哪?CSS 顺序和层叠陷阱
位置决定是否生效。很多人把深色规则写在最前面,结果被后面普通规则覆盖,怎么都切不动。
CSS 层叠规则没变:相同选择器权重下,后写的样式胜出。所以深色模式规则必须放在默认样式之后,且选择器权重不能更低。
- 错误写法:
body { background: white; }<br>@media (prefers-color-scheme: dark) { body { background: black; } }→ 正确,但若上面还有body.theme-dark { background: #333; }就会被覆盖 - 推荐结构:先写基础样式(light 为默认),再用
@media块整体覆盖,所有深色相关声明集中放一起 - 慎用
!important:它会破坏后续 JS 动态换肤逻辑,也干扰用户自己的浏览器插件(如 Dark Reader)
要不要加 JS 手动切换开关?兼容性和维护成本怎么看
纯 CSS 的 prefers-color-scheme 只响应系统设置,不提供用户主动切换入口。加开关看似友好,实际引入新问题。
核心矛盾在于:JS 切换本质是“覆盖系统偏好”,而浏览器不会因此改变 matchMedia 的返回值,导致 CSS 媒体查询失效,只能靠 class 控制——这就退化成传统主题切换方案,和 prefers-color-scheme 没关系了。
- 如果加开关,建议完全放弃
@media,改用data-theme="dark"+ CSS 自定义属性,更可控 - 如果坚持用媒体查询,就别加开关;或者只做“跟随系统”+“强制亮色”两种模式(跳过深色),避免三态混乱
- 移动端 Safari 对
prefers-color-scheme的支持从 iOS 13 开始,旧版本回退到 light 模式即可,无需 polyfill
最易被忽略的一点:深色模式不只是换个背景黑。输入框聚焦边框、滚动条 thumb、SVG 图标 fill、甚至 box-shadow 的模糊色,全都要重新评估明度和对比。一个 @media 块包不住整站视觉,得逐个组件验证。










