如何在 CSS 中让浏览器选择对比色

你是否曾希望能够编写简单的 CSS 来声明一个颜色,然后让浏览器决定是搭配黑色还是白色?现在你可以了,使用 contrast-color()。以下是它的工作原理。

想象一下我们正在构建一个网站或一个 Web 应用,设计要求一堆具有不同背景颜色的按钮。我们可以创建一个名为 --button-color 的变量来处理背景颜色。然后在不同的情况下,从我们的设计系统中为该变量分配不同的值。

有时按钮背景是深色,按钮文本应为白色以提供对比度。其他时候,背景是浅色,文本应为黑色。就像这样

Two buttons side by side. White text on dark purple for the first, black text on pink background for the second.

当然,我们可以使用第二个变量来定义文本颜色,并同时仔细定义 --button-color--button-text-color 的值,成对管理,以确保文本颜色的选择是正确的。但是,在一个大型项目、大型团队中,仔细管理这些细节可能会变得非常困难。突然间,一个深色按钮的文本变成了不可读的黑色,用户无法弄清该做什么。

如果我们能告诉 CSS 将文本设置为黑色/白色,并让浏览器选择使用哪一个——选择与特定颜色对比度更高的那个,那就更容易了。这样我们就可以只管理多种背景颜色,而不用担心文本颜色。

这正是 contrast-color() 函数将允许我们做到的。

contrast-color()

我们可以在 CSS 中这样写

color: contrast-color(purple);

然后浏览器会将 color 设置为黑色或白色,选择与 purple 对比度更好的那个。

让我们来美化我们的按钮。我们将按钮背景色设置为我们的变量。我们将文本颜色定义为与该变量搭配的对比黑/白选项。

button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
}

现在我们只需要定义一种颜色,另一种就会随之而来!当我们更改按钮颜色时,浏览器会重新考虑文本应该是黑色还是白色,并重新选择对比度更高的选项。

为了好玩,我们还可以使用相对颜色语法定义一个悬停颜色,现在一个变量决定了四种颜色——默认按钮颜色及其搭配的文本,以及悬停颜色及其搭配的文本。

:root {
  --button-color: purple;
  --hover-color: oklch(from var(--button-color) calc(l + .2) c h);
}
button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  text-box: cap alphabetic; /* vertically centers the text */
}
button:hover {
  background-color: var(--hover-color);
  color: contrast-color(var(--hover-color));
}

这里有一个结果演示。在Safari Technology Preview中尝试一下,在那里你可以动态改变按钮颜色。

可访问性考虑和对比度算法

现在,你可能会很想相信 contrast-color() 会神奇地自行解决所有对比度可访问性问题,并且你的团队再也不必考虑颜色对比度了。不,根本不是这样。

使用 contrast-color() 函数并不能保证最终的颜色对是可访问的。选择一种颜色(在本例中是背景色),它可能与黑色或白色都没有足够的对比度。仍然需要相关人员——设计师、开发人员、测试人员等等——来确保有足够的对比度。

事实上,如果你现在(本文发表于 2025 年 5 月)在 Safari Technology Preview 中尝试我们的演示,你会发现许多与中间色调背景色的搭配并没有产生足够的对比度。通常看起来做出了错误的选择。例如,这个 #317CFF 蓝色会返回黑色作为对比色。

Medium dark blue button with black text. The text is hard to see.

然而白色显然是感知对比度更好的选择。

Same dark medium blue button, now with white text. Much easier to see what it says.

这里发生了什么?为什么会做出对比度较低的选择?

嗯,Safari Technology Preview 中目前的实现使用的是 WCAG 2(Web 内容可访问性指南第 2 版)中正式定义的对比度算法。如果我们用这个蓝色通过一个备受推崇的 WebAIM 颜色对比度检查器,它确实明确建议使用黑色作为文本颜色,而不是白色。WCAG 2 是当前 Web 可访问性的权威标准,在许多地方都是法律要求。

WCAG 2 算法计算出黑底 #317CFF 的对比度为 5.45:1,而白底 #317CFF 的对比度为 3.84:1。contrast-color() 函数只是选择了数值更大的选项——5.45 大于 3.84。

Screenshots of the WCAG 2 color contrast checker, showing results of white on blue and black on blue. Black passes. White fails. But black is hard to read while white is easy to read.
Web AIM 的 WCAG 2 颜色对比度检查器中测试中等深蓝色上的黑色和白色。

当机器运行 WCAG 2 算法时,黑色文本在数学上具有更高的对比度。但当人类查看这些组合时,黑色文本在感知上具有较低的对比度。如果你觉得这很奇怪,那么你不是唯一一个。WCAG 2 颜色对比度算法长期以来一直是批评的对象。事实上,更新 WCAG 到 3 级的主要驱动力之一就是希望改进对比度算法。

可访问感知对比度算法 (APCA) 是可能被纳入 WCAG 3 的一个候选方案。你今天就可以在 apcacontrast.com 使用 APCA 对比度计算器来尝试这个算法。让我们看看它对这种特定深浅的蓝色背景上的黑色和白色文本的看法。

Screenshot of APCA Contrast Calculator, showing the same tests of black on blue vs white on blue. White clearly wins.
APCA 对比度计算器中,测试相同的中等深蓝色上的黑色和白色。

这种对比度算法评估蓝底黑字得分为 Lc 38.7,而蓝底白字得分为 Lc -70.9。要知道哪个对比度更高,暂时忽略负号,比较 38.7 和 70.9。数字越大,对比度越高。APCA 测试结果表明白色文本明显优于黑色。这感觉完全正确。

(在 APCA 评分系统中,负数仅表示文本比背景亮。可以理解为亮色模式=正数,暗色模式=负数。)

为什么 APCA 提供比 WCAG 2 更好的结果?因为它使用的算法是感知对比度计算,而不是简单的数学计算。这考虑了人类对色相和亮度对比度的感知并非线性的事实。如果你了解过 LCH 与 HSL 颜色模型,你可能听说过新的颜色数学方法在理解我们对亮度的感知以及识别哪些颜色具有相同亮度或色调方面做得更好。“Lc”标记 APCA 分数代表“亮度对比度”,例如“Lc 75”。

幸运的是,contrast-color 函数背后的算法可以替换。此功能的支持于 2021 年 3 月首次在 Safari Technology Preview 122 中推出。(此外,当时它被命名为 color-contrast。)那时,选择更好的算法还为时过早。

CSS 标准仍然要求浏览器使用旧算法,但包含一个关于未来的注释:“目前只支持 WCAG 2.1,但已知此算法存在问题,尤其是在深色背景下。此模块的未来修订版可能会引入额外的对比度算法。”关于哪种算法最适合 WCAG 3 的辩论仍在进行中,包括对正在考虑的算法许可的讨论。

同时,你的团队在选择调色板时仍应非常小心,并牢记可访问性。如果你为对比色选择明显浅色或明显深色的颜色,即使使用 WCAG 2 算法作为支撑,contrast-color() 也能很好地工作。在评估与中间色调的对比度时,算法的结果开始有所不同。

此外,即使更新了更好的算法,单独的 contrast-color() 函数也永远无法保证可访问性。“这个对比度更高”与“这个对比度足够”不是一回事。有许多颜色与黑色或白色都没有足够的对比度,尤其是在较小的文本尺寸或较细的字体粗细下。

在现实世界中提供足够的对比度

在考虑颜色对比度时,我们应该记住我们工具箱中的另一个工具,以确保我们为每个人提供良好的对比度——prefers-contrast 媒体查询。它允许我们为那些需要更多对比度的人提供替代样式。

@media (prefers-contrast: more) {
  /* styling with more contrast */
}

让我们来思考如何在实际情况中使用这些工具。想象一下,我们正在为一家树苗圃创建网站,其主要品牌颜色是特定色调的亮中绿色。我们的设计团队非常希望将 #2DAD4E 作为主要按钮背景色。

为了简单起见,我们还假设我们生活在一个 APCA 算法已经取代 CSS 中 WCAG 2 算法的未来。这一变化意味着 contrast-color() 将为我们的文本颜色(与这种中等绿色对比)返回白色,而不是黑色。

但是查看这种颜色组合,我们发现对于某些用户来说,对比度可能不够,特别是如果文本很小的话。这就是良好设计的重要性。

Testing white on medium green in the APCA contrast calculator. The interface has lots of options for adjusting the colors. And it's got a panel across the bottom with six sections of examples of white text on this color green, in various sizes and weights of fonts.

当使用这种绿色作为白色文本的背景时,APCA 分数为 Lc -60.4。

你可能还记得 WCAG 2 以比率(如“2.9:1”)评估对比度。然而,APCA 分数是一个单一数字,范围从 Lc -108 到 106。Lc -60.4 是否有足够的对比度取决于文本大小——以及 APCA 新增的字体粗细。

APCA 可读性标准中,有关于青铜、白银和黄金级别符合度良好目标的信息。这些建议可以真正帮助设计师选择文本的大小和粗细,以确保足够的对比度,同时允许一系列美丽的颜色组合。事实上,WCAG 3 本身的设计旨在提供灵活的指导,帮助你了解如何支持所有用户,而不是像 WCAG 2 那样进行二元判断。良好的可访问性不是简单地满足一个神奇的指标来在列表中打勾。它是关于理解什么对真实的人有效,并为他们设计。而人们的需求是复杂的,而不是二元的。

你会注意到,这个特定的APCA 对比度计算器不仅提供分数,还评估了动态示例的成功性,展示了字体大小和字体粗细的组合。在我们的例子中,“Usage”显示为“fluent text okay”(对于上面蓝底黑字的例子,它显示为“Usage: spot & non text only”)。计算器显示,在 #2DAD4E 背景上使用白色文本,如果字体粗细为 400 或更粗,24px 的文本就可以正常工作。如果我们想使用 300 的字体粗细,那么文本至少应为 41px。当然,这取决于我们使用的字体,而且我们使用的字体与该对比度计算器不同,但这种指导比 WCAG 2 算法的工具更具细微差别。它有助于我们的团队制定一个漂亮的设计方案。

我们的苗圃网站支持亮色和暗色模式,我们的设计师确定 #2DAD4E 作为按钮颜色在亮色和暗色模式下对许多用户都适用,只要他们仔细设计按钮,考虑字体大小和粗细如何影响对比度。但即使考虑到这些,Lc -60.4 对所有用户来说对比度仍然不够,因此对于任何已将其可访问性偏好设置为要求更高对比度的用户,我们将用两个选项替换按钮背景颜色——亮色模式下使用较深的 #3B873E 绿色(白色文本,得分为 Lc -76.1),暗色模式下使用较浅的 #77e077 绿色(黑色文本,得分为 Lc 75.2)。

这是我们虚构的设计团队希望我们在 CSS 中实现的调色板

A diagram of our color palette, explaining when to use which color combination. (All information is also articulated in the text of this article.)

当我们用变量定义颜色时,在这些各种条件下交换颜色值变得异常容易。而且通过使用 contrast-color(),我们只需关注背景颜色,而无需担心文本颜色搭配。我们将让浏览器完成这项工作,并免费获得配对颜色。

为了同时实现所有这些,我们只需编写这段代码(因为,请记住,我们假装生活在未来,那时更好的算法已经取代了 CSS 中的 WCAG 2 算法)

--button-color: #2DAD4E;  /* brand green background */ 

@media (prefers-contrast: more) {
  @media (prefers-color-scheme: light) {
    --button-color: #419543;  /* darker green background */
  }
  @media (prefers-color-scheme: dark) {
    --button-color: #77CA8B;  /* lighter green background */
  }
}

button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  font-size: 1.5rem;  /* 1.5 * 16 = 24px at normal zoom */
  font-weight: 500;
}

实际上,由于 WCAG 2 算法正在驱动 contrast-color(),我们可能无法在这个网站上使用它。但如果我们有另一个项目,品牌颜色是深绿色,并且黑/白之间的选择是正确的,那么它今天可能会很有帮助。

在使用 contrast-color() 时,它在定义多种状态或选项(如启用/禁用、亮色/暗色模式、偏好对比度等)的颜色时特别有用。

超越黑白

你可能会问:“但是如果我想让浏览器选择的颜色不仅仅是黑/白呢?”如果你读过或尝试过四年前 Safari Technology Preview 122 中的原始实现,你可能会记得原始功能做了更多的事情。新的 contrast-color() 函数比原始的 color-contrast() 大大简化了。

由于关于 WCAG 3 使用哪种颜色对比度算法的决定仍在争论中,CSS 工作组决定推出一个只选择黑色或白色与第一个颜色形成对比的工具。保持简单使得以后可以替换算法。通过硬编码选项列表为黑色/白色,当 WCAG 2 算法被替换时,网站崩溃的可能性大大降低,这赋予了 CSSWG 所需的灵活性,使其能够持续进行必要的更改,即使 contrast-color 已经交付给用户使用。

未来,更复杂的工具将会出现,以支持更强大的选项。也许你将能够列出一组自定义颜色选项,让浏览器从中选择,而不是从黑色/白色中选择。也许你会列出一组选项,并指定一个你希望浏览器达到的对比度级别,而不是让它选择产生最大对比度的选项。

与此同时,通常只需要在黑白之间做出简单选择就足够了。我们希望更快地将简单版本交到你手中,而不是等待一个需要数年时间的过程。

虽然以上所有示例都展示了彩色背景上的黑/白文本,但 contrast-color 可以用于更多用途。你可以为文本使用自定义颜色,并将背景设置为黑/白。或者根本不涉及文本,而是定义边框、背景——任何东西的颜色。你可以做的事情很多。

继续对话

你可以通过阅读创建 APCA (可访问感知对比度算法)的团队的文档来了解更多信息。包括

我们很乐意听取您对 contrast-color() 的看法。您对此工具的反馈有助于塑造其未来。您可以在 Bluesky / Mastodon 上找到我,Jen Simmons。或者关注我们的其他网络布道师——Saron Yitbarek 在 BlueSky,Jon Davis 在 Bluesky / Mastodon。您也可以在 LinkedIn 上关注 WebKit。