先看数字,因为数字会让人以为这是错的
| 角色 | 对底色中位 | 全库范围 | 达到 1.5:1 的 |
|---|---|---|---|
边框 / 分割线(border、divider) | 1.21:1 | 1.10 到 1.39:1 | 0 套 |
卡片 / 表面(surface、card) | 1.04:1 | 1.03 到 1.10:1 | 0 套 |
禁用态(btnDisabled) | 1.59:1 | 1.21 到 3.30:1 | 约七成未达 |
第一眼的判断通常是「这组数据有问题」。1.04:1 意味着卡片和底色的亮度几乎一致,那卡片怎么看得见?
答案是:它本来就不是靠颜色被看见的。
为什么这么低还能看
拿一张卡片来说,它在屏幕上是可辨识的,靠四件事,颜色只排最后:
- 位置与留白。卡片周围有 16 或 24 像素的空隙,空隙的颜色与卡片不同,边界就被「空」画出来了。这是最主要的一层。
- 阴影。阴影不是颜色角色,是渲染出来的效果,能表达高度。库里没有把阴影写进配色,因为它不属于色值。
- 圆角。一个 12px 的圆角,比任何边框都更能说明「这是一块独立的区域」。
- 极小的色差。就是那 1.04:1。
所以 1.04:1 不是设计缺陷,是设计意图:卡片应该在留白里浮起来,而不是在描边里框起来。这种做法在 iOS 之后成了主流,因为描边会让界面显得像表格,而极小的色差让界面显得干净。
边框这一层同理,1.21:1 大约是 #e2e8f0 压白底的水平。它存在感很弱,但它真的存在,因为人眼对细线的边缘比对大面积色差更敏感。
什么时候会翻车
这套做法在两种情况下会失效,而两种都很常见。
第一,把配色导出给别人用。本站的 CSS 变量导出就是这样:拿到 --surface 和 --bg 的人没有你的间距、你的圆角、你的阴影,只有两个几乎一样的颜色。他自己排一版,卡片就真的看不见了。这不是他做错了,是这一段信息在传递过程中丢了。
第二,低质量屏幕或强光环境。1.04:1 的差异在 OLED 手机上调到一半亮度时基本消失,户外更是完全消失。这时候唯一还在工作的分层是留白和阴影,如果那两样也没做,界面就真的平了。
所以判据应该这样定:色值上的分层可以极低,但前提是位置与阴影这两层必须在。如果只能交出颜色(换主题、交给第三方、平台不允许阴影),那颜色这层就不能那么低。
如果只能靠颜色,这三个值可用
- 边框:做到 1.5:1 以上,人眼才稳定识别为一条线。1.21:1 在好屏幕上是线,在差屏幕上是噪点。给一个具体值:白底上的边框不要浅于
#d4d4d8这一档。 - 卡片:做到 1.15:1 以上,浮起感才成立。1.04:1 离开了阴影就不再是卡片。深色模式要反过来,卡片比底色更亮,同样需要 1.15:1 的差距。
- 禁用态:只压颜色不够。
btnDisabled中位 1.59:1,本身在告诉你「这块不可用」,但它不告诉你「文字还读不读得到」。禁用按钮上的字仍然要能看清,通常靠降低不透明度加cursor: not-allowed来表达,而不是把整个色值压到看不见。
这三个值不是标准,是从「在差屏幕上仍然能分辨」倒推出来的经验下限。真正稳妥的做法是两层都用:颜色给一点,位置和阴影给剩下的。
一个容易判错的角色:小程序的 tag
小程序的 tag 常被误判。它压底色的对比度中位只有 2.91:1,看起来像一个不及格的文字色。
但 tag 不是文字,它是标签的背景,上面还要压深色文字。所以拿 4.5:1 去要求它是错的:它是一个浅底,越是浅底,压在上面的字才越清楚。
真正要检查的是「tag 底 + tag 上的文字」这一对,而不是「tag 底 + 页面底色」这一对。这个区别在检查表里很容易漏:同一个 4.5 门槛,适用于文字,不适用于背景。判断一个颜色该不该过对比度,先问它承载的是字还是面。
深色模式里,这三层要反过来
上面所有数字都来自浅色底。深色底的规则不是「同样做但换成深色」,方向要反过来,而且更容易做坏。
- 卡片要比底色更亮,不能更暗。浅色底上卡片压暗表示下陷?不是,浅色底上卡片通常压得更白表示浮起。深色底同理:卡片要比底色亮一档。做反了,卡片看起来像被按下去了一个坑。
- 边框也要更亮。深色底上的
#1a1a1a边框压在#121212底色上,比值只有 1.1 左右,比浅色底更难看见,因为深色的亮度差异在数值上被压缩了。 - 禁用态要比可用态更暗、更淡。方向对了,但幅度容易过头。深色底上再把按钮压暗,很容易变成一团黑,用户看不出那是一个按钮。
另一个具体差别:深色底对色相更敏感。同一个色相角,在浅底上看起来是灰的,在深底上会显出色偏。所以在深色模式里做分层,光靠明度差经常不够,得同时控制饱和度,否则一块「中性灰」的卡片会在深底上泛蓝或泛黄。
可执行的两条:深色卡片比底色亮到 1.15:1 以上,深色边框找到 1.5:1 以上。这两个数字和浅色模式一样,但方向相反,而方向做错的界面看起来很安静:它不报错,只是所有东西都像是凹进去的。
数据怎么来的
上面所有比值都是「该角色对同套配色底色」的对比度,按 WCAG 2.1 相对亮度公式计算,由本站库内 69 套带分层角色的配色现算。分层阈值参考的是「人眼能否在普通屏幕上分辨一条细线」,不是 WCAG 的文本门槛,因为 WCAG 对非文本元素的 3:1 要求针对的是「控件边界必须可辨识」,而卡片与底色属于装饰性分层,不在其约束范围内。这两件事经常被混在一起引用。