教程·网页7 分钟阅读·Updated 2026 年 10 月·ExodeUI 团队
平滑的明暗模式转换:令牌、CSS 和运动
如何构建一个尊重系统设置、避免加载时闪烁、设置本机控件样式和动画而不影响可访问性的明/暗主题切换。
直接总结和定义
核心要点
在语义标记上构建主题,默认情况下遵循首选颜色方案的系统设置,让人们覆盖它,并设置颜色方案属性以使表单控件和滚动条匹配。在页面呈现之前应用保存的主题以避免闪烁。保持切换动画简短,例如快速交叉淡入淡出或视图转换,并在减少运动时跳过它。
从系统设置开始
首选颜色方案媒体功能会告诉您用户是否向操作系统请求浅色或深色主题。使用它作为默认值,并添加一个覆盖它并被记住的应用内开关。
带有令牌的主题,而不是覆盖
将颜色定义为语义标记(表面、文本、边框、重音),并为每个主题赋予自己的值,通常作为根元素上的 CSS 自定义属性。组件在主题之间永远不会改变;只有代币值可以。这与多品牌设计系统使用的三层令牌方法相同。
不要忘记本机控件
MDN 解释说 color-scheme 属性告诉浏览器元素支持哪种方案,这会更改表单控件、滚动条和其他内置 UI 的默认颜色。没有它,黑暗的页面可以显示明亮的白色输入和滚动条。
避免加载时出现闪光
如果在页面渲染后通过 JavaScript 应用保存的主题,人们会看到错误主题的闪烁。在样式表绘制之前,尽早应用它,在文档头中使用一个小的内联脚本或服务器渲染的属性。
仔细制作开关动画
大约 150 到 250 毫秒的短暂交叉淡入淡出使这种变化感觉是经过深思熟虑的。避免在每个元素上转换每个颜色属性,这在大页面上可能会很慢;单个视图转换快照通常更平滑。使用标签或工具提示使切换本身清晰,并在启用减少运动时删除动画。
生产实施片段
交互组件实现
/* Tokens per theme */
:root { color-scheme: light dark; --surface: #ffffff; --text: #121212; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) { --surface: #121212; --text: #f2f2f2; }
}
:root[data-theme="dark"] { color-scheme: dark; --surface: #121212; --text: #f2f2f2; }
:root[data-theme="light"] { color-scheme: light; }
body { background: var(--surface); color: var(--text); }
// Switch with a View Transition where supported, instantly otherwise
function setTheme(theme) {
const apply = () => { document.documentElement.dataset.theme = theme; localStorage.setItem('theme', theme); };
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!document.startViewTransition || reduce) return apply();
document.startViewTransition(apply);
}要记住的关键 Principle
- 默认为首选颜色方案;让人们推翻并记住它
- 带有语义标记的主题,而不是每个组件的覆盖
- 设置颜色方案以使本机控件匹配
- 首次绘制之前应用保存的主题
- 保持开关动画简短并尊重减少的动作
常见问题解答
来源
- prefers-color-scheme — MDN 网络文档
- color-scheme — MDN 网络文档
- View Transition API — MDN 网络文档