Палитра выглядит удачной на макете, а на телефоне под солнцем серый текст исчезает. Дело не во вкусе: у контраста есть измеримая величина и формальные пороги. Если их не учитывать, часть посетителей просто не прочитает страницу.

Почему «красиво» не значит «читаемо»

Человек с нормальным зрением различает оттенки, которые при сниженной контрастной чувствительности сливаются. Сюда же добавляются блики на экране, дешёвые матрицы и масштабирование. Поэтому доступность опирается не на восприятие дизайнера, а на отношение яркостей.

Отдельная ловушка — тёмная тема. Светло-серый #8b8b8b на почти чёрном фоне кажется мягким и «премиальным», но по цифрам может не дотягивать до минимума. Глаз адаптируется, а формальная проверка — нет.

Как считается контраст

Берутся относительные яркости двух цветов, от 0 до 1, и подставляются в формулу:

(L1 + 0.05) / (L2 + 0.05)

Где L1 — яркость более светлого цвета, L2 — более тёмного. Результат от 1 до 21. Чем больше число, тем выше контраст. Белый на чёрном даёт 21, одинаковые цвета — 1.

Яркость канала считается нелинейно: сначала значение делится на 255, затем применяется гамма-коррекция. Простое среднее арифметическое RGB даст неверный результат, особенно на насыщенных цветах.

const channel = (v) => {
  v /= 255;
  return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
};

function luminance(hex) {
  const n = parseInt(hex.replace('#', ''), 16);
  const r = channel((n >> 16) & 255);
  const g = channel((n >> 8) & 255);
  const b = channel(n & 255);
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function contrast(a, b) {
  const [l1, l2] = [luminance(a), luminance(b)].sort((x, y) => y - x);
  return (l1 + 0.05) / (l2 + 0.05);
}

console.log(contrast('#8b8b8b', '#1e1e1e').toFixed(2));

Пороговые значения

WCAG 2.2 задаёт два уровня, которые чаще всего требуют от сайтов:

  • AA: 4.5:1 для обычного текста и 3:1 для крупного;
  • AAA: 7:1 для обычного и 4.5:1 для крупного.

Крупным считается текст от 24 пикселей обычного начертания или от 18.66 пикселя полужирного. Порог 3:1 действует и для элементов интерфейса: границ полей, иконок, индикатора фокуса.

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

Прозрачность, градиенты и картинки

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

Если текст кладётся на фотографию, фон лучше подготовить заранее: убрать лишнее через удаление фона и подложить ровный цвет.

С градиентами та же история. Кнопка с переходом от светлого к тёмному может пройти проверку в начале и провалиться в конце. Смотрите оба края и середину.

Тонкие линии и разделители тоже важны: граница поля ввода с контрастом 1.4 видна на макете, но не видна на ноутбуке при ярком свете. Для границ интерфейса ориентируйтесь на 3:1.

Цвет как единственный сигнал

Ошибка в форме, отмеченная только красным, недоступна части пользователей. Добавьте текст, иконку или подчёркивание. Это требование 1.4.1 «Использование цвета» и здравый смысл одновременно.

То же с ссылками в тексте: если они отличаются только цветом и он близок к основному, ссылку трудно найти. Подчёркивание решает проблему без потери контраста.

Как проверить свою палитру

Соберите основные пары «текст — фон»: основной текст, второстепенный, ссылка, кнопка, поле ввода, фокус. Для каждой посчитайте контраст. На сайте есть генератор палитр: он показывает оттенки и помогает подобрать цвета, но финальную проверку лучше делать по формуле выше или в инструментах разработчика.

В браузере откройте DevTools, выберите элемент и посмотрите вычисленный цвет. Не проверяйте по скриншоту: сжатие и цветовой профиль меняют значения. И помните, что rgba(255, 255, 255, 0.6) на фоне #1e1e1e — это не то же самое, что #a0a0a0.

Про APCA и будущее WCAG 3

Формула WCAG 2.x критикуется за то, что плохо описывает тёмные темы и тонкие шрифты. В проекте WCAG 3 предлагается метрика APCA, учитывающая размер и начертание. Пока стандарт не принят, ориентируйтесь на WCAG 2.2 AA: этого требуют аудиты и государственные закупки.

Коротко

  • контраст считается по относительной яркости, а не по среднему RGB;
  • AA: 4.5:1 для текста, 3:1 для крупного текста и интерфейса;
  • проверяйте худший участок на фото и градиентах;
  • не передавайте смысл только цветом;
  • тёмная тема требует отдельной проверки, а не копирования светлой.