纯 CSS 实现系统级暗黑模式切换,必须以@media (prefers-color-scheme: dark)为唯一可靠入口点,且所有主题变量需在:root明确定义默认值(浅色),再于该媒体查询中完整覆盖;漏定义、顺序错、未加color-scheme: light dark声明或忽略表单/SVG/滚动条适配,均会导致深色下白底黑字、对比失常或控件失效。

纯 CSS 实现系统级暗黑模式切换,@media (prefers-color-scheme: dark) 是唯一可靠入口点——它不依赖 JS,加载即生效,但必须满足变量定义位置、顺序和兜底三要素,否则页面在深色系统下仍会白底黑字或对比失常。
为什么 @media (prefers-color-scheme: dark) 写了却没反应
常见错误是把深色规则写在浅色样式之前,或嵌套在其他媒体查询里。CSS 优先级按声明顺序覆盖,@media 块本身不提升权重。
- 深色规则必须放在所有默认(浅色)样式之后,否则被后续无条件规则覆盖
- 禁止嵌套:
@media (max-width: 768px) { @media (prefers-color-scheme: dark) { ... } }在旧版 Safari 中完全失效 - 不能只写
dark分支:系统设为no-preference或浏览器不支持时,变量若未在:root初始化,就会继承浏览器默认值(比如canvas色可能意外变白) - 确保 CSS 文件被正常加载——动态插入的
或通过 JS 创建的CSSStyleSheet,部分浏览器不触发prefers-color-scheme匹配
:root 里变量怎么初始化才不会翻车
所有主题变量必须在 :root 中明确定义默认值(以浅色为基准),再用媒体查询覆盖。变量不是“全局常量”,作用域和声明时机直接影响渲染结果。
- 错例:
:root { --bg: #fff; } @media (prefers-color-scheme: dark) { :root { --bg: #121212; } }—— 看似合理,但 iOS 13.3 以下 Safari 可能忽略后者的:root重定义 - 正例:先写完整
:root默认,再用媒体查询整体覆盖,且所有变量名带语义,如--color-bg-surface而非--bg1 - 漏定义 = 翻车:若
button { background: var(--btn-primary-bg, #007bff); },而深色下--btn-primary-bg未定义,就会回退到#007bff(亮蓝),在深灰背景上刺眼 - 系统色要显式桥接:
color: CanvasText;本身响应系统,但若你用了var(--text-primary),就必须在媒体查询中给它赋值,不能指望浏览器自动映射
表单、SVG、滚动条这些“隐形元素”怎么一起切
仅改自定义变量不够,原生控件(input、select、textarea、滚动条、SVG fill)默认不读取 CSS 变量,需额外声明或重置。
- 加
color-scheme: light dark;到:root或html,这是通知浏览器“我支持双主题”,让原生控件自动适配(Chrome 85+、Firefox 96+、Safari 13.1+) - SVG 必须显式绑定:
svg { fill: var(--icon-primary); },且确保--icon-primary在深色分支中已定义;否则白色 SVG 在深色背景下直接消失 - 表单控件边框/背景易被忽略:
input, select { background: var(--form-bg); border-color: var(--border-muted); },别依赖transparent或硬编码#ccc - 滚动条颜色无法用变量控制,需单独写伪元素:
::-webkit-scrollbar-thumb { background: var(--scroll-thumb); },并确保该变量已定义
手动切换按钮和系统偏好冲突怎么办
用户点了“切深色”按钮,但系统是浅色,此时媒体查询不匹配,@media (prefers-color-scheme: dark) 不生效——纯 CSS 方案天然不支持手动覆盖,必须引入 JS + data-theme 层级控制。
- 不要用 JS 直接操作
style.setProperty()改大量变量,卡顿明显;应只切data-theme="dark",再用 CSS 规则覆盖变量 - 优先级顺序必须是:
[data-theme="dark"]>@media (prefers-color-scheme: dark)> 默认值,靠选择器权重实现 - 监听系统变化时,只在用户未手动设置过主题时才响应:
if (!localStorage.getItem('theme')) { /* 应用 systemDark */ } - 刷新后状态丢失?必须在 JS 初始化阶段就读
localStorage.getItem('theme')并同步设到document.documentElement,不能等 DOM 加载完再补
最易被忽略的是:深色模式不是“把背景变黑、文字变白”,而是整套色彩语义的重新锚定。哪怕 @media 规则生效了,如果 --text-secondary 在深色下还是用 #666,对比度就大概率低于 WCAG AA 标准——得用 lch() 或工具校验亮度层级,而不是凭感觉调色。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











