必须处理系统切换闪烁、颜色变量fallback、第三方组件覆盖三件事:用matchMedia检测初始值并设data-theme类防闪,CSS变量在媒体查询中定义并加默认值,第三方库通过[data-theme]类覆盖且注意选择器优先级。

直接结论:用 @media (prefers-color-scheme: dark) 套 CSS 规则就能适配,但真正在意体验的项目,必须处理「系统切换时的闪烁」「颜色变量 fallback」「第三方组件覆盖」这三件事。
为什么 prefers-color-scheme 不能只写一遍就完事
浏览器读取系统偏好是异步的,CSSOM 构建早于该媒体查询生效,导致页面先按亮色渲染、再闪一下变暗色——尤其在 iOS Safari 和部分 Android WebView 里特别明显。另外,prefers-color-scheme 不支持 JS 动态监听变更(直到 matchMedia API 出现),纯 CSS 方案对运行时切换无响应。
- 必须在
中内联一段轻量级 JS,用matchMedia('(prefers-color-scheme: dark)')检测初始值,并给加data-theme="dark"类 - 所有核心颜色规则要同时写两套:
[data-theme="dark"] .btn和@media (prefers-color-scheme: dark) .btn,前者防闪,后者保语义 - 避免只依赖
@media,否则 SSR 或首屏直出时无法预判主题
:root 里定义 CSS 变量时怎么设 fallback
CSS 变量本身不感知媒体查询,--bg: #fff 在暗色下不会自动变成 #121212。硬写两套 :root 会冲突,且无法利用系统偏好做默认值。
- 正确做法:在顶层
:root中用color-scheme: light dark告诉浏览器你支持双模式(影响表单控件等原生元素) - 变量定义放媒体查询里:
@media (prefers-color-scheme: dark) { :root { --bg: #121212; --text: #e0e0e0; } } - 关键 fallback:所有用到变量的地方加默认值,比如
background: var(--bg, #ffffff),防止变量未定义时样式崩塌 - 别用
!important覆盖变量,会导致后续 JS 主题切换失效
第三方 UI 库(如 Ant Design、MUI)怎么不被覆盖
这些库自带暗色主题开关,但它们通常靠 JS 注入 class 或修改 :root 变量,和你的 prefers-color-scheme 规则打架——要么库的主题不生效,要么你的 CSS 被库的 reset 覆盖。
立即学习“前端免费学习笔记(深入)”;
- 优先查文档:MUI v5+ 支持
useMediaQuery('(prefers-color-scheme: dark)'),Ant Design 5.1+ 有algorithm: theme.darkAlgorithm配合系统检测 - 如果库不支持,用
[data-theme="dark"]类强制接管,例如[data-theme="dark"] .ant-btn { background: var(--primary-dark); } - 注意 specificity:库的 CSS 选择器往往带很多层级,你的覆盖规则至少要比它多一级,比如用
html[data-theme="dark"] .ant-input - 避开全局重置:不要用
* { background: var(--bg) }这类暴力写法,第三方组件内部结构可能意外被染色
最麻烦的不是写对规则,而是得同时应付「服务端没传主题头」「iOS 13 之前不支持该媒体查询」「用户手动开了深色但字体渲染发虚」这些边界情况。留个 data-theme 的钩子,比死磕纯 CSS 更实际。


















