
怎么用 @media (prefers-color-scheme) 检测系统主题
浏览器能直接读取系统深色/浅色偏好,靠的就是这个媒体查询。它不是 JavaScript 判断,也不依赖 class 切换,纯 CSS 原生支持,兼容性已覆盖 Chrome 76+、Firefox 67+、Safari 12.1+ 和 Edge 79+。
常见错误是写成 @media (prefers-color-scheme: dark) { ... } 却没加根选择器或漏掉 fallback —— 浏览器默认按浅色渲染,如果只写深色规则,浅色下就完全没样式。
- 必须同时提供浅色(默认)和深色两套规则,深色规则用
@media (prefers-color-scheme: dark)包裹 - 不要在
@media外再套:root变量赋值逻辑,变量得在各自媒体查询内重新声明 - 移动端 Safari 对嵌套
@media支持不稳定,避免在@layer或@container内部再用它
颜色变量怎么设才不踩坑
很多人直接在 :root 里定义 --bg: #fff,再在 @media (prefers-color-scheme: dark) 里覆盖,结果发现暗色模式下部分组件背景还是白的——问题出在 CSS 优先级和变量作用域上。
根本原因是:CSS 变量是动态继承的,但媒体查询块内的变量声明不会“回写”到 :root,而组件若直接用了未声明的变量,就会 fallback 到初始值或透明。
立即学习“前端免费学习笔记(深入)”;
- 把所有主题相关变量都放在各自的
@media块里,比如浅色下:root { --bg: #fff; --text: #333; },深色下@media (prefers-color-scheme: dark) { :root { --bg: #1a1a1a; --text: #eee; } } - 避免用
!important强行覆盖,它会破坏变量链,导致后续 JS 动态改主题失效 - 按钮边框、阴影等非纯色属性也要同步更新,比如
box-shadow: 0 1px 3px rgba(0,0,0,0.1)在深色下得改成rgba(0,0,0,0.3)才看得清
哪些样式容易被忽略但影响体验
光换背景和文字颜色远远不够。很多细节在深色模式下会直接“消失”或反直觉,比如图片、表单控件、代码块、甚至 SVG 的 fill 颜色。
典型现象:深色模式下 <input type="date"> 的下拉箭头变成黑底黑箭头;<img> 在深色背景上边缘发虚;<pre> 里的代码高亮背景仍是白色。
-
<img>加background: var(--bg)防透明图发灰;带 alpha 的 PNG 建议统一加image-rendering: -webkit-optimize-contrast - 表单元素如
<select>、<textarea>必须显式重置color、background、border-color,不能只信appearance: none - 代码块用
pre, code分别设置,尤其注意code的background和color要跟主题一致,否则高亮插件生成的 inline style 会打架
要不要加手动切换开关
加开关本身不难,但一加就容易破坏原生逻辑。用户开了系统深色模式,你网页却强制显示浅色,或者 JS 切了主题后 @media (prefers-color-scheme) 突然失效,都是因为混用了两种控制源。
真正可靠的做法是:以系统偏好为默认,仅用 JS 存储用户手动选择,并在页面加载时用 document.documentElement.setAttribute('data-theme', 'dark') 注入标记,CSS 里用 [data-theme="dark"] 覆盖媒体查询。
- 不要用 JS 直接改
document.body.style,那会覆盖全部 CSS 规则,包括动画和响应式断点 - localStorage 存的是字符串,记得 JSON.parse / stringify,不然切完刷新变 undefined
- 服务端渲染(SSR)场景下,首次 HTML 必须包含正确
data-theme,否则会有闪屏
深色模式真正的复杂点不在怎么写,而在「谁说了算」:系统偏好、用户手动选择、网站默认设定,三者优先级一旦没理清,后面所有样式都会反复回归 bug。










