直接用 @media (prefers-color-scheme: dark) 和 light 媒体查询可原生响应系统主题,无需JS;需在CSS中分别定义两套样式,避免!important破坏层叠,兼顾表单、SVG、阴影等细节,并注意Safari及WebView兼容性问题。
直接用 prefers-color-scheme 媒体查询就能响应系统级暗黑/亮色偏好,不需要 JS 监听或手动切换逻辑——前提是用户没在浏览器里强制覆盖系统设置。
@media (prefers-color-scheme) 写基础适配这是最轻量、最符合语义的方案,CSS 原生支持,无 JS 依赖,页面加载即生效。
<style> 标签内,不能用内联 style 属性light、dark、no-preference(极少见,可忽略)@media (prefers-color-scheme: dark) { body { background: #121212; color: #e0e0e0; } a { color: #bb8eff; }}@media (prefers-color-scheme: light) { body { background: #ffffff; color: #333333; } a { color: #4285f4; }}
注意:两个规则可以共存,但不要用 !important 覆盖对方,否则会破坏层叠逻辑。
data-theme 属性 + JS 是常见补充手段因为 prefers-color-scheme 只能读系统偏好,无法应对用户主动点击「切换主题」按钮的需求。
立即学习“前端免费学习笔记(深入)”;
matchMedia('(prefers-color-scheme: dark)').matches,设 <html data-theme="dark">
[data-theme="dark"] 选择器,而非媒体查询 —— 这样 JS 切换才可控localStorage.setItem('theme', 'dark'),否则刷新丢失matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ...)
看似简单,但线上出问题往往卡在这几处:
@media (prefers-color-scheme) 的解析有 bug,嵌套在 @layer 或某些预处理器输出中可能失效,建议单独提一层写<input>、<select>)、滚动条、box-shadow、SVG fill 都得一并调整,否则视觉割裂#000000:OLED 屏幕易烧屏,推荐 #121212 或 #1e1e1e
真正难的不是第一次适配,而是后续维护:每次新加组件,都要检查它在两种模式下的对比度、焦点态、禁用态是否可读可用。系统偏好只是起点,不是终点。