WebKit

How to have the browser pick a contrasting color in CSS

2025年5月13日 作者:Jen Simmons

你是否曾经希望可以使用简单的 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));
}

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

为了增加趣味性,我们还可以使用 Relative Color Syntax 来定义悬停颜色,现在一个变量决定了四种颜色——默认的按钮颜色和与之搭配的文本,以及悬停颜色和与之搭配的文本。

: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 蓝色返回的 contrast-color 是黑色。

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.Testing black versus white on a medium-dark blue in the WCAG 2 color contrast checker at Web AIM.

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

Accessible Perceptual Contrast Algorithm (APCA) 是包含在 WCAG 3 中的一个可能的候选者。你今天可以通过使用 apcacontrast.com 上的 APCA Contrast Calculator 来尝试这种算法。让我们看看它对在这种特定深浅的蓝色背景上的黑色与白色文本的看法。

Screenshot of APCA Contrast Calculator, showing the same tests of black on blue vs white on blue. White clearly wins.Testing the same black versus white on a medium-dark blue in the APCA Contrast Calculator.

这种对比度算法评估黑色-蓝色为 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 query。它允许我们为那些想要更多对比度的人提供替代样式。

@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. When using this shade of green as the background for white text, the APCA score is Lc -60.4.

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

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

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

你会注意到,这种特定的 APCA Contrast Calculator 不仅提供分数,还评估动态示例的成功,这些示例显示字体大小和字体粗细的组合。在我们的例子中,对于“Usage”,它说“fluent text okay”。(对于上面的蓝色背景上的黑色示例,它反而说“Usage: spot & non text only”。)计算器显示,如果字体粗细为 400 或更粗,则白色文本在 #2DAD4E 上以 24px 文本工作。如果我们想使用字体粗细 300,那么文本应至少为 41px。当然,这将取决于我们使用的字体,并且我们没有使用与 Contrast Calculator 相同的字体,但此指导比 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() 的算法,我们可能无法在此网站上使用它。但是,如果我们有另一个项目,其中品牌颜色是较深的绿色,并且白色/黑色之间的选择是正确的选择,那么今天它可能会非常有帮助。

当为多个状态或选项(如启用/禁用、浅色/深色模式、prefers-contrast 等)定义颜色时,使用 contrast-color() 特别有用。

超越黑色和白色

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

由于尚未就 WCAG 3 使用哪种颜色对比度算法做出决定,因此 CSS Working Group 决定继续使用一种工具,该工具仅选择黑色或白色与第一种颜色形成对比。保持简单可以稍后交换算法。通过将选项列表硬编码为黑色/白色,当 WCAG 2 算法被替换时,网站不太可能崩溃,这使 CSSWG 具有保持进行所需更改所需的灵活性,即使 contrast-color 已交付到用户手中。

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

与此同时,通常只需要在黑色和白色之间进行简单的选择。我们希望尽快将简单版本交付给你,而不是等待需要数年的过程。

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

继续对话

你可以通过阅读 创建它的人 的文档来了解有关 APCA(Accessible Perceptual Contrast Algorithm)的更多信息。包括:

我们很乐意听到你对 contrast-color() 的想法。你对此工具的反馈可以帮助塑造它的未来。你可以在 Bluesky / Mastodon 上找到我,Jen Simmons。或者关注我们的其他 Web 宣传员——Saron Yitbarek 在 BlueSky 上,Jon Davis 在 Bluesky / Mastodon 上。你也可以在 LinkedIn 上关注 WebKit。

NextRelease Notes for Safari Technology Preview 219Learn more PreviouslyWebKit Features in Safari 18.5Learn more