Front-End-Checklist 深色模式实践:用 prefers-color-scheme 与 CSS 自定义属性构建可维护的主题系统
发布时间:2026/9/18 23:19:00 作者:尧图编辑部 阅读量:1,286

Front-End-Checklist 深色模式实践用 prefers-color-scheme 与 CSS 自定义属性构建可维护的主题系统【免费下载链接】Front-End-Checklist The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist本篇指南围绕 Front-End-Checklist 仓库中的dark-mode-css规则展开讲解如何用prefers-color-scheme媒体查询 CSS 自定义属性Custom Properties实现自动适配系统偏好的深色模式并在此基础上支持用户手动切换、原生控件配色、图片降亮与平滑过渡。读完本文你将掌握一套可直接落地的语义化设计令牌design token主题方案并能用仓库提供的检查清单与源码证据完成自查。规则背景为什么深色模式是前端必备能力Front-End-Checklist 将dark-mode-css归类为css/design-tokens子类目下的规则见 规则元数据优先级为 medium、难度为 intermediate、预计耗时 25 分钟。规则的核心主张是深色模式的最佳实现方式是在媒体查询或[data-theme]选择器中重新定义 CSS 自定义属性的值而不改动任何组件样式。这条规则依赖的底层能力来自两条相邻规则css-custom-properties把颜色、间距、字体等设计系统值统一定义在:root上深色模式本质上是在媒体查询里重新定义自定义属性的值color-oklch与 dark-mode-css 同属css/design-tokens区域两者常被一起评审——用感知均匀的 oklch 色彩空间构建色板能显著降低手工搭建深色模式色阶的难度。规则文档的完整内容存放于 skills/dark-mode-css/SKILL.mdAgent 使用入口与其 references/rule.md完整实现细节。快速参考四条核心结论使用media (prefers-color-scheme: dark)应用深色模式样式将浅色/深色颜色定义为:root上的 CSS 自定义属性便于一键切换支持手动切换把偏好存入localStorage并在根元素上设置data-theme属性确保深色模式的配色满足 WCAG 对比度要求。为什么值得做需求动因与工程价值超过一半的用户出于减轻视疲劳的目的偏好深色模式尤其是在低光照环境下对光敏感photosensitivity的用户更是依赖它。通过prefers-color-scheme实现深色模式尊重用户操作系统层面的偏好用户无需在站点内寻找开关而 CSS 自定义属性让这一切成为干净的、可维护的增量而非一次破坏性重构。用自定义属性实现主题化的关键工程价值在于一次定义、处处生效。硬编码颜色散落在各个 CSS 文件里一次换肤rebrand、一次深色模式适配、一次间距调整都要逐个查找和修改几十行代码自定义属性把值集中起来一处变更全局传播并且支持运行时主题runtime theming——这是 Sass/Less 这类预处理器变量做不到的见 css-custom-properties 规则的 Why It Matters。核心实现语义化令牌 媒体查询重定义第一步定义语义化颜色令牌而不是dark-blue正确做法是定义语义化的令牌名称——不是--color-dark-blue这种描述具体外观的名字而是--color-primary、--color-surface这类描述用途的名字。这样深色模式只需要重定义同一组变量/* ✅ Define semantic color tokens — not dark-blue but color-primary */ :root { /* Light mode values (default) */ --color-surface: #ffffff; --color-surface-elevated: #f9fafb; --color-text: #111827; --color-text-muted: #6b7280; --color-border: #e5e7eb; --color-primary: #2563eb; --color-primary-foreground: #ffffff; --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05); } /* Dark mode: just redefine the same variables */ media (prefers-color-scheme: dark) { :root { --color-surface: #0f172a; --color-surface-elevated: #1e293b; --color-text: #f1f5f9; --color-text-muted: #94a3b8; --color-border: #334155; --color-primary: #3b82f6; --color-primary-foreground: #ffffff; --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.3); } } /* Components use variables — no dark-mode-specific component CSS needed */ .card { background: var(--color-surface-elevated); border: 1px solid var(--color-border); color: var(--color-text); box-shadow: var(--shadow-sm); }这套代码中值得注意的设计要点默认值即浅色模式:root中默认就是浅色值不写媒体查询时页面也能正常渲染深色模式只重定义变量组件.card始终使用var(...)因此不需要任何深色模式专属的组件 CSS阴影也纳入令牌体系深色模式下阴影透明度从0.05提到0.3因为深色背景上的阴影几乎不可见需要更强的深度表现。第二步让颜色系统与感知对齐进阶如果进一步采用 oklch 色彩空间见 color-oklch 规则深色模式只需要调整**明度轴Lightness**即可获得感知一致的色阶/* Dark mode — adjust only the lightness axis */ media (prefers-color-scheme: dark) { :root { --color-text: var(--color-neutral-50); --color-text-muted: var(--color-neutral-400); --color-bg: var(--color-neutral-950); --color-bg-surface: var(--color-neutral-900); --color-border: var(--color-neutral-800); } }原因在于 sRGB 色彩空间不具备感知均匀性——10% 的明度变化在不同色相上看起来差异巨大而 oklch 的 L 通道在任何色相/彩度下都产生相同的感知亮度变化这使它特别适合构建深色模式色板和一致的 hover/active 状态。支持手动切换data-theme localStorageprefers-color-scheme尊重系统偏好但有些用户希望站点内独立控制。规则给出的方案是用data-theme属性覆盖操作系统偏好并用localStorage持久化。CSS 侧属性选择器定义覆盖值/* Allow>// Manual toggle with localStorage persistence function setTheme(theme) { document.documentElement.setAttribute(data-theme, theme) localStorage.setItem(theme-preference, theme) } // On load — respect saved preference or OS default const saved localStorage.getItem(theme-preference) if (saved) { document.documentElement.setAttribute(data-theme, saved) } // If no saved preference, the media query handles it automatically这里的关键设计是优先级分层用户显式保存的偏好 系统偏好。没有保存过偏好的用户媒体查询会自动接管——两种路径互不冲突。仓库源码印证matchMedia 检测偏好本仓库中 Front-End-Checklist 站点自身就提供了用户偏好检测的参考实现见 apps/web/lib/accessibility/preferences.ts/** Returns the users preferred color scheme (light, dark, or no-preference). */ export function prefersColorScheme(): light | dark | no-preference { if (typeof window undefined) return no-preference if (window.matchMedia((prefers-color-scheme: dark)).matches) return dark if (window.matchMedia((prefers-color-scheme: light)).matches) return light return no-preference }同一文件还提供了prefersReducedMotion()对应prefers-reduced-motion与prefersHighContrast()对应prefers-contrast: more说明前端偏好检测是一个统一的工具层话题深色模式只是其中之一。另外站点在 apps/web/app/layout.tsx 中通过 Next.js 的viewport.themeColor配置让浏览器地址栏/主题色随prefers-color-scheme自动切换export const viewport: Viewport { width: device-width, initialScale: 1, maximumScale: 5, themeColor: [ { media: (prefers-color-scheme: light), color: #ffffff }, { media: (prefers-color-scheme: dark), color: #09090b } ] }原生控件的配色color-scheme 属性很多开发者只处理了页面背景和文字颜色却忽略了滚动条、表单输入框、日期选择器等浏览器原生控件——它们在深色页面下会保持刺眼的白色。color-scheme属性告诉浏览器使用深色原生控件:root { color-scheme: light dark; /* Browser adjusts scrollbars, form inputs, etc */ } [data-themedark] { color-scheme: dark; }:root { color-scheme: light dark; }表示同时允许两种配色浏览器根据当前系统偏好渲染对应原生控件[data-themedark] { color-scheme: dark; }在用户手动切到深色时强制使用深色原生控件。图片在深色模式下的降亮处理截图和示意图通常基于浅色背景制作直接放到深色页面上会显得刺眼。规则给出了一个简单有效的降亮方案/* Reduce image brightness in dark mode (useful for screenshots and diagrams) */ media (prefers-color-scheme: dark) { img:not([src*.svg]) { filter: brightness(0.85) contrast(1.05); } }这里特意排除了 SVGimg:not([src*.svg])因为 SVG 常作为图标使用其颜色应当跟随当前主题令牌而非被统一降亮。平滑过渡切换主题不闪变主题切换瞬间跳变会让人感到生硬给颜色类属性加上过渡即可/* Add a smooth transition when switching themes */ :root { transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease; } /* But skip transition on page load */ .no-transition * { transition: none !important; }只对background-color、color、border-color做过渡——这些都是合成友好的属性不会触发布局抖动相关规则见 animation-performance 同类思路颜色过渡用 CSS transitions 而非引发布局变更的手段首次加载时给根元素加上.no-transition类避免页面初始化时出现一次从浅色过渡到深色的闪烁动画。检查清单如何用 SKILL 进行代码评审SKILL.md见 skills/dark-mode-css/SKILL.md定义了四个评审动作适用于人工评审或 Agent 驱动的检查动作内容Check检查 CSS 是否支持深色模式查找prefers-color-scheme用法或data-theme属性模式识别任何在深色模式下会显示异常的硬编码颜色Fix将颜色提取为 CSS 自定义属性并在prefers-color-scheme: dark媒体查询内重新定义它们Explain解释如何用 CSS 自定义属性、prefers-color-scheme媒体查询和 JS 开关实现深色模式Code Review审查样式表、组件样式与响应式状态在渲染出的 UI 中精确定位违反规则的 selector、声明或断点评审时重点关注是否存在绕过令牌直接硬编码的颜色值组件是否在媒体查询中重复写了整套深色样式而不是只重定义变量手动开关与系统偏好是否形成了正确的优先级验证清单上线前逐项确认规则的 Verification 部分见 references/rule.md要求在规则影响的断点和交互状态下检查渲染出的 UI在 DevTools 中确认计算样式computed styles与预期修复一致上线前至少在一个移动端视口和一个桌面端视口下测试如果规则影响动效、对比度或布局稳定性直接验证这些面向用户的结果。另外结合 SKILL 元数据category: cssestimatedTime: 25 分钟与规则正文建议补充以下实操检查对比度验证深色模式下文字/背景对需满足 WCAG 2.1 对比度要求普通文本 4.5:1大文本 3:1。注意 oklch 的 L 通道与 WCAG 相对亮度不是一回事仍需用对比度检查工具实测见 color-oklch 的对比度说明FOUC 预防手动切换场景下若 JS 在首屏后才设置data-theme用户可能短暂看到错误的主题——可考虑内联一段首屏脚本如同 preferences.ts 的检测逻辑尽早恢复偏好两种路径都测既测试跟随系统偏好不设置任何存储值也测试手动覆盖写入localStorage后刷新。总结深色模式不是一个加一个 media query 改几个颜色的简单活而是一次设计系统层面的治理机会。Front-End-Checklist 的dark-mode-css规则给出的完整路径是语义化令牌自定义属性→ 媒体查询/属性选择器重定义 → localStorage 持久化 → color-scheme 处理原生控件 → 图片降亮 → 平滑过渡 → 多视口验证。这套方案既尊重系统偏好prefers-color-scheme又允许用户手动覆盖data-theme同时把组件样式与主题色彻底解耦是值得在任意现代 Web 项目中直接复用的架构模式。【免费下载链接】Front-End-Checklist The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考