黑暗不是目标
深色模式真正成功,是在整体亮度变化后,信息层级和阅读舒适度仍然成立。把白色换成黑色,再机械地反转其他颜色,通常只会得到刺眼文字、浑浊边框,以及比正文更抢眼的强调色。
应当先定义语义 token,而不是为每个组件单独指定颜色。Token 描述的是职责,因此数值变化时,不需要重写组件的表达方式。
:root {
color-scheme: light dark;
--surface: #f6f4ef;
--surface-raised: #ffffff;
--text: #1b1b19;
--muted: #68675f;
--border: #d8d5cc;
--accent: #315ea8;
}
@media (prefers-color-scheme: dark) {
:root {
--surface: #151513;
--surface-raised: #1e1e1b;
--text: #f2f0e8;
--muted: #aaa79d;
--border: #393832;
--accent: #8bb8ff;
}
}
不要让每一个抬高表面都按相同比例变亮。层级、分组和强调也可以通过间距与边框表达。如果每张卡片都在深色页面上发光,整个界面会迅速变得嘈杂。
不要只检查色板
| 场景 | 检查重点 | 常见失败 |
|---|---|---|
| 长文章 | 阅读舒适度与链接可见性 | 纯白文字产生眩光 |
| 代码块 | 语法对比度与横向溢出 | 弱化 token 完全消失 |
| 禁用控件 | 状态是否仍可辨认 | 看起来仍可操作 |
| 外部图片 | 图片边缘处理 | 明亮图片支配整个页面 |
| 焦点状态 | 键盘当前位置是否明确 | 轮廓与边框融在一起 |
| 错误状态 | 离开颜色后仍能表达含义 | 只有红色这一种信号 |
- 降低屏幕亮度后文字仍可阅读。
- 链接不只依赖颜色表达。
- 边框可见,但不会像发光一样突出。
- 原生控件遵循
color-scheme。 - 页面跳转和刷新后仍保存主题选择。
- 焦点与错误状态保持明确。
- 桌面和移动端截图都经过检查。
- 动效遵循 reduced-motion 偏好。
测试切换与例外
主题切换可能暴露许多平时看不到的问题:样式加载前的闪烁、仍使用旧颜色的浏览器控件,或者已经按上一套色板渲染的图表。需要测试首次访问、硬刷新、后退导航,以及页面打开时系统主题突然变化。
是否应该自动压暗所有图片?
不应该。自动压暗会改变作品本身,也可能让照片失真。更稳妥的方法是使用轻微边框或周围表面来控制视觉关系,只对自己掌控的装饰资产做特殊处理。如果图片是论述的重要部分,应当保留它原本的明暗。
好的深色模式并不是另一套视觉身份,而是用不同的光线预算表达同一个信息层级。