Вам когда-нибудь хотелось, чтобы можно было написать простой CSS для объявления цвета, после чего браузер сам бы определял, чёрный или белый должен сочетаться с этим цветом? Теперь это возможно благодаря contrast-color(). В статье мы объясним, как это работает.

Представьте, что вы разрабатываете веб-сайт или веб-приложение, и в дизайне требуется куча кнопок с разными цветами фона. Для обработки цвета фона можно создать переменную --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 ещё и цвет наведения курсора (hover); теперь одна переменная определяет четыре цвета — цвет кнопки по умолчанию и связанный с ним цвет текста плюс цвет hover и его цвет текста.

: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; /* вертикальное центрирование текста */
}
button:hover {
  background-color: var(--hover-color);
  color: contrast-color(var(--hover-color));

Демо результата можно посмотреть на Codepen. Попробуйте его в Safari Technology Preview, где можно динамически менять цвет кнопки.

Вопросы accessibility и алгоритмы расчёта контрастности

Было бы приятно думать, что contrast-color() волшебным образом самостоятельно решит все вопросы accessibility и контрастности, а вашей команде никогда больше не понадобится думать о контрасте цветов. Но всё не совсем так.

Применение функции contrast-color() не гарантирует, что получившаяся пара цветов обеспечит accessibility. Вполне может быть так, что при выборе цвета (в нашем случае фонового) у него не будет достаточной контрастности ни с чёрным, ни с белым. Для обеспечения достаточности контраста всё равно требуется вмешательство людей — дизайнеров, разработчиков, тестеров и других.

Вы можете сами попробовать наше демо в 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 Content Accessibility Guidelines version 2). Если мы введём этот синий цвет в программу проверки контрастности WebAIM, она чётко рекомендует использовать чёрный цвет текста. WCAG 2 — официальный на данный момент стандарт accessibility в вебе, его соблюдение законодательно требуется во многих местах.

Алгоритм 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.
T

Когда машины выполняют алгоритм WCAG 2, чёрный цвет математически более контрастен. Но когда на эти сочетания смотрят люди, чёрный цвет воспринимается менее контрастным. И это кажется странным не только вам. Алгоритм расчёта контрастности цветов WCAG 2 уже давно подвергается критике. На самом деле, одна из самых веских причин апгрейда WCAG до уровня 3 — это желание улучшить алгоритм контрастности.

Один из потенциальных кандидатов на включение в WCAG 3 — это Accessible Perceptual Contrast Algorithm (APCA). Можно протестировать этот алгоритм уже сегодня, воспользовавшись APCA на сайте apcacontrast.com. Давайте посмотрим, что он думает о сравнении чёрного и белого на этом конкретном оттенке синего.

Screenshot of APCA Contrast Calculator, showing the same tests of black on blue vs white on blue. White clearly wins.

Этот алгоритм даёт чёрному на синем оценку Lc 38.7, а белому на синем — оценку Lc -70.9. Чтобы понять, у какого варианта контрастность выше, забудем ненадолго о знаке минус и сравним 38.7 с 70.9. Чем больше число, тем больше контрастность. Результаты теста APCA чётко гласят, что белый текст лучше чёрного. И это совпадает с нашими ощущениями.

(В системе оценок APCA отрицательное число просто означает, что текст светлее, чем фон. Можно считать, что светлый режим = положительные числа, тёмный режим = отрицательные числа.)

Почему APCA даёт такие хорошие результаты по сравнению с WCAG 2? Потому что алгоритм вычисляет контрастность на основе восприятия, а не только простой математикой. Он учитывает, что люди не воспринимают контрастность линейно для любых оттенков и яркости. Если вы читали о разнице между цветовыми моделями LCH и HSL, то, вероятно слышали о том, что новые подходы к математике цвета лучше понимают наше восприятие яркости и знают, какие какие цвета будут иметь одинаковую яркость или тон. Маркировка Lc в оценке APCA означает «Lightness contrast» («контрастность светлоты»), например, «Lc 75».

К счастью, алгоритм функции contrast-color можно заменить. Поддержка этой возможности появилась в марте 2021 года в Safari Technology Preview 122. (Тогда она называлась color-contrast.) В те времена было ещё слишком рано выбирать более качественный алгоритм.

Стандарт CSS по-прежнему призывает браузеры использовать старый алгоритм, но содержит заметку о будущем: «На данный момент поддерживается только WCAG 2.1, однако известно, что этот алгоритм имеет проблемы, в частности, с тёмными фонами. В будущих ревизиях этого модуля, скорее всего, будут добавлены новые алгоритмы контрастности». По-прежнему продолжаются дебаты о том, какой алгоритм будет лучше для WCAG 3, в том числе и обсуждения вопроса лицензирования рассматриваемых алгоритмов.

А пока вашей команде по-прежнему следует внимательно выбирать цветовые палитры, не забывая об accessibility. Если вы выбираете в качестве контрастного цвета чётко светлые или чётко тёмные цвета, то contrast-color() замечательно будет справляться, даже несмотря на то, что в ней используется алгоритм WCAG 2. Результаты работы алгоритмов начинают разниться только в случае вычисления контраста для средних градаций.

Кроме того, сама по себе функция contrast-color() никогда не гарантировала accessibility даже при использовании более совершенного алгоритма. Утверждение «эта пара имеет больше контраста» отличается от «эта пара имеет достаточно контраста». Существует множество цветов, недостаточно контрастных и с белым, и с чёрным, особенно при малых размерах текста или весе шрифтов.

Обеспечение достаточной контрастности в реальных сценариях

При рассуждениях о цветовых контрастах нам нужно помнить, что в нашем арсенале есть ещё один инструмент для обеспечения качественного контраста для всех — медиазапрос prefers-contrast. Он позволяет нам предложить альтернативную стилизацию для тех, кому хочется больше контраста.

@media (prefers-contrast: more) {
  /* стилизация с большей контрастностью */
}

Давайте подумаем, как применять эти инструменты в реальных сценариях. Представим, что мы создаём веб-сайт для лесопитомника, чей основной цвет бренда — конкретный оттенок ярко-зелёного. Наша команда дизайнеров хочет использовать в качестве основного фона кнопок #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 Readability Criterion. Эти рекомендации сильно помогают дизайнерам в выборе размера и веса текста для обеспечения достаточной контрастности, в то же время допуская применение широкого спектра красивых сочетаний цветов. Сам WCAG 3 проектируется таким образом, чтобы предоставить дизайнерам гибкие инструкции, позволяющие понять, как поддерживать потребности всех пользователей, а не принимать двоичные решения, как это делает WCAG 2. Хорошая accessibility — это не просто обеспечение волшебной метрики, позволяющей поставить галочку в списке. Для неё важно понимание того, что подходит живым людям, и создание дизайне с учётом этого. А у людей сложные потребности, они не двоичны.

Можно заметить, что этот конкретный APCA Contrast Calculator не только показывает оценку, но и определяет успех динамических примеров, показывая разные сочетания размеров и весов шрифтов. В нашем случае, в разделе «Usage» написано «fluent text okay» («подходит для беглого чтения текста»). (В примере с чёрным на синем он говорит «Usage: spot & non text only» («использовать только для пятен и нетекстовых сочетаний»).) Калькулятор показывает, что белый текст на #2DAD4E воспринимается как текст размером 24px, если вес шрифта 400 или больше. Если мы хотим использовать текст весом 300, то текст должен иметь размер не меньше 41px. Разумеется, это будет зависеть от конкретного начертания шрифта, а мы не используем тот же шрифт, что и Contrast Calculator, но в этих инструкциях гораздо больше нюансов, чем у инструментов алгоритма WCAG 2. И они помогают нашей команде создавать планы красивого дизайна.

Наш веб-сайт лесопитомника поддерживает и светлый, и тёмный режим, а наши дизайнеры определили, что #2DAD4E подходит многим пользователям в качестве цвета кнопки и в светлом, и в тёмном режиме, если кнопки будут тщательно проектироваться с учётом влияния на контрастность размера и веса шрифта. Но даже с учётом всего этого, Lc -60.4 — это недостаточный контраст для всех пользователей, поэтому для всех, кто настроил параметры accessibility так, чтобы запрашивалась повышенная контрастность, мы заменяем цвет фона кнопки на два варианта — более тёмного зелёного #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() нам следует беспокоиться только о фоновых цветах, а не о сочетаниях с цветами текста. Мы заставим браузер заниматься этой работой, без лишних усилий получив пары цветов.

Чтобы реализовать всё это одновременно, достаточно написать этот код (потому что, повторюсь, мы притворились, что живём в будущем, когда на замену WCAG 2 в CSS пришёл более совершенный алгоритм):

--button-color: #2DAD4E;  /* зелёный фон бренда */ 

@media (prefers-contrast: more) {
  @media (prefers-color-scheme: light) {
    --button-color: #419543;  /* тёмно-зелёный фон кнопок */
  }
  @media (prefers-color-scheme: dark) {
    --button-color: #77CA8B;  /* светло-зелёный фон кнопок */
  }
}

button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  font-size: 1.5rem;  /* 1.5 * 16 = 24px при стандартном зуме */
  font-weight: 500;
}

Однако в реальности, поскольку функцией contrast-color() управляет алгоритм WCAG 2, мы, скорее всего, не сможем использовать такой код на сайте. Но если бы у нас был другой проект, где бренд имеет более тёмный зелёный цвет, а выбор между чёрным и белым был бы корректным, то этот код оказался бы полезен и сегодня.

Применение contrast-color() особенно полезно при определении цветов для различных состояний или опций, например, «включено-выключено», светлый/тёмный режим, prefers-contrast и так далее.

Не только чёрный и белый

Вы можете задаться вопросом: «Но что, если я хочу, чтобы браузер выбирал не только между чёрным и белым?» Если вы читали о нашей исходной реализации в Safari Technology Preview 122 четыре года назад или пробовали её самостоятельно, то можете помнить, что изначальная фича была способна на гораздо большее. Новая функция contrast-color() была сильно упрощена по сравнению с исходной color-contrast().

Так как вопрос о том, какой алгоритм color-contrast нужно использовать в WCAG 3, всё ещё обсуждается, CSS Working Group решила двигаться дальше, выбрав инструмент, который просто выбирает в качестве контрастного к первому цвету чёрный или белый. Такое упрощение позволит заменить алгоритм в будущем. Ограничив список вариантов только чёрным и белым, веб-сайты с гораздо меньшей вероятностью поломаются после замены алгоритма WCAG 2, обеспечивая CSSWG гибкость, необходимую для внесения необходимых изменений даже после того, как contrast-color станет доступна пользователям.

В будущем появятся более сложные инструменты, способные поддерживать ещё более мощные опции. Возможно, мы сможем создавать список пользовательских вариантов цвета, чтобы браузер мог выбирать их них, а не только из чёрного или белого. Может быть, мы сможем создать список вариантов плюс указывать уровень контрастности, к которому должен стремиться браузер, а не выбирать вариант, обеспечивающий максимальную контрастность.

Ну а пока часто вполне достаточно простого выбора между чёрным и белым. Мы хотим, чтобы у пользователей как можно раньше появилась простая версия и им не приходилось ждать несколько лет.

И хотя во всех примерах выше используется чёрный/белый текст на цветном фоне, contrast-color можно применять для гораздо большего. Можно выбрать пользовательский цвет для своего текста и сделать так, чтобы фон был чёрным/белым. Или вообще не касаться текста и определять цвета для границ, фона и так далее.

Дальнейшее чтение

Подробнее об APCA (Accessible Perceptual Contrast Algorithm) вы можете узнать, прочитав документацию создавших его авторов. В том числе:

  • The Easy Intro to the APCA Contrast Method — простое введение в тему перцептивно-равномерного контраста

  • Bronze Simple Mode — «самое базовое» руководство по дизайну, предназначенное для пользователей, мигрирующих с алгоритма WCAG 2

Комментарии (2)


  1. Kwisatz
    20.05.2025 17:24

    Вот только есть нюанс, часто уровень контраста нужно понижать для лучшей читаемости, особенно при большого количестве текста. В этом, кстати трюк превью и скриншотов, особенно в темплейтах, большинство из них просто непригодны для использования.


  1. PaulZi
    20.05.2025 17:24

    Я делал автоцвет фильтром:

    filter: invert(1) grayscale(1) contrast(1000);