Меню
Поиск

Интерфейс и визуал · Цвет и контраст

Как описать цветовую систему, по которой новый цвет попадает в продукт без вас

Пока палитра — привычка одного дизайнера, каждый новый экран приносит ещё один синий, а тёмная тема ломается на первом вписанном коде цвета. В уроке разберём примитивы и роли, как их устроили Material, Atlassian, Carbon, Primer и GOV.UK, почему экран не берёт примитив напрямую, таблицу контраста пар ролей, порядок добавления нового цвета и роли в CSS и Figma. Для практики — магазин кофе в двух темах и ревью чужого макета по строкам системы.

Пример для всего урока. Магазин кофе из урока про контраст. У него есть палитра с ролями и тёмная тема, но живут они в макете одного дизайнера. Теперь над продуктом работают трое, появились оптовый раздел и промо-страницы акций, и каждый новый экран приносит «ещё один синий». Опишем цветовую систему, по которой новый цвет попадает в продукт без автора палитры.

1Цвет по правилу, а не по вкусу

Пока палитра — привычка одного человека, новый цвет попадает в продукт, потому что понравился.

Через три месяца работы втроём в макетах магазина нашлись:

  • оранжевый для баннера акции;
  • второй зелёный для статуса «Доставлен»;
  • синий кнопки, вписанный в оптовый раздел прямо кодом цвета.

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

Цветовая система отвечает на этот вопрос сама. В ней пять частей:

  1. Примитивы — все цвета продукта со шкалами оттенков.
  2. Роли — зачем цвет на экране: фон, текст, действие, ошибка.
  3. Две темы — у каждой роли значение для светлой и для тёмной.
  4. Таблица контраста — какие пары ролей проходят нормы и насколько.
  5. Правило добавления — как в систему попадает новый цвет.

2Примитивы и роли

Примитив говорит, какой это цвет. Роль говорит, зачем он.

  • Примитив — blue-600, gray-50. Это значение со своим местом в шкале. Он не знает, где будет стоять, и не меняется от темы.
  • Роль — action, surface, error. Она ссылается на примитив, и ссылка зависит от темы: в светлой action — это blue-600, в тёмной — blue-300.

«Серый-100» живёт внутри системы. Наружу — в макеты и в код экранов — выходит только роль: «поверхность», «вторичный текст».

Экран говорит, зачем ему цвет, а система решает, какой он.

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

3Как это устроено в больших системах

Названия разные, устройство общее: слой значений и слой смысла.

СистемаЗначенияСмыслЧто говорит о прямом использовании
Material Design 3Reference: md.ref.palette.primary40System: md.sys.color.primary, on-primary, surface, error, outline. Ещё есть component-токены, они ссылаются на systemТема переключается на уровне system-токенов: primary в светлой теме — primary40, в тёмной — primary80
AtlassianПалитра, например Red100 и Red1000color.background.danger, color.text.subtle, color.border.focused, color.icon.warningПрибитые значения их линтер считает нарушением. И отдельно: не берите токен только потому, что цвет совпал, — в другой теме это сломается
IBM CarbonПалитра IBM: $blue-60$background, $layer-01, $text-primary, $support-error, $focus, $interactiveТокены, в отличие от кодов цвета, действуют во всех четырёх темах: White, Gray 10, Gray 90, Gray 100
GitHub PrimerBase: color-scale-pink-5Functional: borderColor-sponsors-emphasis, есть ещё уровень компонентовБазовые токены не учитывают тему и не должны использоваться напрямую ни в коде, ни в макете
GOV.UKgovuk-colour("red")govuk-functional-colour("error"), focus — #ffdd00Не копируйте коды цвета. Жёлтый фокуса — только для фокуса

У Atlassian есть ещё третий тип цвета — акценты: десять цветов без смысла, для меток и категорий, которые выбирает сам человек.

Их нельзя брать там, где у цвета есть значение, например для предупреждения.

Это ответ на вопрос «а какой цвет дать тегу»: не роль статуса, а отдельный набор без смысла.

4Почему примитив нельзя брать напрямую

Экран на примитивах выглядит правильно ровно до смены темы.

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

Карточка заказа · на ролях и на примитивах

На ролях

Заказ № 10381 190 ₽
3 сентября✕ Отменён
Подробнее о заказе
Повторить

На примитивах

Заказ № 10381 190 ₽
3 сентября✕ Отменён
Подробнее о заказе
Повторить
Фон карточки в обоих случаях берётся из роли, поэтому он темнеет вместе с темой. Текст справа вписан значениями и остаётся цветами светлой темы.
Что стало с контрастом в тёмной теме
МестоНа роляхНа примитивах
Номер и суммаЗаказ 14,88 : 1Заказ 1,07 : 1
Дата3 сентября 7,82 : 13 сентября 3,00 : 1
СтатусОтменён 6,68 : 1Отменён 3,18 : 1
СсылкаПодробнее 7,30 : 1Подробнее 3,06 : 1
Основной текст на примитиве в тёмной теме пропадает совсем: 1,07 : 1. Остальное проваливает норму 4,5 : 1.

Что ещё ломает прямой примитив

  • Смену палитры. Бренд решил сделать синий чуть теплее. Роль action меняется в одном месте, а каждый вписанный #1D5FD1 надо искать по всем макетам и всему коду.
  • Смысл. По #C23030 в коде не понять, ошибка это или просто красный заголовок акции. По error понятно сразу, и его можно найти поиском.
  • Совпадение цветов. Сегодня у разделителя и у неактивной рамки один серый. Если взять для рамки «роль разделителя, раз цвет тот же», следующая правка разделителя сломает рамку. Atlassian об этом прямо предупреждает.

5Роли продукта в двух темах

Одна таблица на обе темы: у каждой роли два значения, и оба — примитивы.

РольЗачемСветлаяТёмная
bgФон карточек и полейgray-0 #FFFFFFgray-950 #15181C
surfaceФон страницы и шапкиgray-50 #F4F6F8gray-850 #1E2227
textОсновной текстgray-900 #1B1F24gray-100 #E8EBEE
text-mutedВторичный текст, подписи полейgray-600 #5B6570gray-300 #A3ADB8
borderРазделители, рамки карточекgray-150 #DCE1E6gray-800 #343A41
border-fieldРамка поля вводаgray-400 #808A94gray-500 #6E7883
actionКнопки, ссылки, фокусblue-600 #1D5FD1blue-300 #7AA7F0
on-actionТекст на кнопкеgray-0 #FFFFFFblue-950 #0B1B33
error и error-surfaceТекст и подложка ошибкиred-600 и red-50red-300 и red-900
warning и warning-surfaceТекст и подложка предупрежденияyellow-700 и yellow-50yellow-300 и yellow-900
success и success-surfaceТекст и подложка успехаgreen-700 и green-50green-300 и green-900
info и info-surfaceТекст и подложка сообщенияblue-600 и blue-50blue-300 и blue-900

У info и action один примитив, но роли разные. Сегодня они совпадают по цвету, завтра info может стать бирюзовым, и кнопки это не заденет.

Роль без тёмного значения не публикуется. Иначе первый же экран с этой ролью в тёмной теме покажет цвет светлой — как правая карточка в разделе 4.

6Таблица контраста

Контраст считают не для цветов, а для пар ролей, которые встречаются на экране.

Material Design 3 строит роли парами — primary и on-primary, surface и on-surface. И обещает у пар минимум 3 : 1, если их использовать в задуманных сочетаниях.

Этого хватает для крупного текста и значков, но не для обычного текста. Поэтому свою таблицу пар стоит считать самим и держать под обычный текст 4,5 : 1.

Пары ролей магазина · светлая и тёмная
ПараНормаСветлаяТёмная
text на bg4,5Аа 16,56 : 1Аа 14,88 : 1
text-muted на bg4,5Аа 5,93 : 1Аа 7,82 : 1
border-field на bg3▬ 3,51 : 1▬ 3,96 : 1
action на bg4,5Аа 5,81 : 1Аа 7,30 : 1
on-action на action4,5Аа 5,81 : 1Аа 7,06 : 1
error на error-surface4,5Аа 4,84 : 1Аа 5,72 : 1
warning на warning-surface4,5Аа 5,40 : 1Аа 7,73 : 1
success на success-surface4,5Аа 4,68 : 1Аа 6,40 : 1
info на info-surface4,5Аа 5,02 : 1Аа 5,85 : 1
Ближе всех к норме пары статусов на своих подложках: 4,68 и 4,84. Любую правку success и error в светлой теме придётся мерить заново.
Таблицу пар проверяет скрипт. Пары записаны ролями, значения берутся из системы, и после каждой правки палитры все пары пересчитываются в обеих темах. Провал останавливает правку.

7Как добавить новый цвет

Новый цвет попадает в продукт по порядку.

Дизайнер промо-страниц хочет оранжевый #FF8A3D для кнопки акции. Вот как это решается по правилу:

  1. Сначала роль, потом цвет. Зачем оранжевый? «Выделить кнопку акции». Это действие, и роль action уже есть. Если нужна вторая, заметная кнопка, это новая роль с именем по смыслу, например action-promo.
  2. Проверить, что роль правда новая. Может быть, хватит существующей: крупный баннер с кнопкой action выделится и без нового цвета.
  3. Предложение коротко: имя роли, где она стоит, почему существующие не подходят, снимок экрана.
  4. Значения для обеих тем из шкалы. Нет подходящего оттенка — в шкалу добавляется новый примитив.
  5. Пары контраста для обеих тем. Белый на #FF8A3D даёт 2,34 : 1 — кнопка с такой надписью не проходит. Проходит, например, #B34700: 5,50 : 1. Или оставить яркий оранжевый, но с тёмной надписью: 7,06 : 1. Выбор между ними — за брендом, а не за одним макетом.
  6. Роль появляется в Figma и в коде одновременно, с датой в журнале изменений системы, и команда о ней узнаёт.
Один экран — ещё не повод для роли. Если оранжевый нужен одному лендингу на неделю, это исключение: оно записывается в журнал и не становится ролью, пока не повторится.

8Роли в коде и в Figma

Работает, когда в макете и в коде одни и те же имена ролей, а примитивы спрятаны внутри системы.

Роли магазина в CSS · пример для разработчика
/* Примитивы — только внутри системы, экраны их не видят */
:root {
  --gray-0: #FFFFFF;   --gray-50: #F4F6F8;  --gray-100: #E8EBEE;
  --gray-300: #A3ADB8; --gray-600: #5B6570; --gray-900: #1B1F24;
  --gray-950: #15181C;
  --blue-300: #7AA7F0; --blue-600: #1D5FD1; --blue-950: #0B1B33;
  --red-300: #F07C7C;  --red-600: #C23030;
}

/* Роли — светлая тема по умолчанию */
:root {
  color-scheme: light dark;
  --color-bg: var(--gray-0);
  --color-text: var(--gray-900);
  --color-text-muted: var(--gray-600);
  --color-action: var(--blue-600);
  --color-on-action: var(--gray-0);
  --color-error: var(--red-600);
}

/* Тёмная тема — те же имена, другие значения */
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: var(--gray-950);
    --color-text: var(--gray-100);
    --color-text-muted: var(--gray-300);
    --color-action: var(--blue-300);
    --color-on-action: var(--blue-950);
    --color-error: var(--red-300);
  }
}

/* Экраны берут только роли */
.button { background: var(--color-action); color: var(--color-on-action); }
.error  { color: var(--color-error); }
Вместо медиазапроса можно писать обе темы в одной строке: --color-bg: light-dark(var(--gray-0), var(--gray-950)). Функция light-dark() работает во всех основных браузерах с мая 2024 года и только вместе с color-scheme: light dark.

В Figma

  • Две коллекции переменных. «Примитивы» — шкалы цветов. «Роли» — переменные, которые ссылаются на примитивы: в Figma это называется alias, и ссылаться можно только на переменную того же типа.
  • Два режима в коллекции ролей — Light и Dark. Переключение режима на фрейме перекрашивает весь экран. Режимы есть на тарифах Professional и выше: до 10 на коллекцию в Professional, до 20 в Organization; на бесплатном Starter их нет.
  • Области применения. У переменной можно ограничить, куда её разрешено ставить: заливка фрейма, фигуры, текста, обводка, эффекты. Роли текста — только в заливку текста, роли фона — только в заливку фреймов.
  • Формат для разработчиков. Сообщество W3C по дизайн-токенам в октябре 2025 года выпустило первую стабильную версию формата — 2025.10, и Figma умеет импортировать токены в нём.

9Проверка чужого макета

С системой замечание звучит так: «Ц1: кнопка залита значением, нужна роль action». Без неё — «какой-то не наш синий».

Строки системы магазина, на которые ссылается ревью:

СтрокаПравило
Ц1Экран берёт цвет только через роль. Примитивы и коды цвета — внутри системы.
Ц2Роль выбирают по смыслу, а не по виду. Совпавший цвет не делает роли взаимозаменяемыми.
Ц3У каждой роли два значения — для светлой и тёмной темы.
Ц4Пары ролей проходят нормы в обеих темах: текст 4,5 : 1, рамки и значки 3 : 1.
Ц5Статус — слово и значок, цвет роли статуса помогает.
Ц6Новый цвет попадает в систему по порядку из раздела 7.
Тренажёр · что это за замечание
  1. 1. «Кнопка „В корзину“ в оптовом разделе залита #1D5FD1 значением».

  2. 2. «Для кнопки акции взят новый оранжевый #FF8A3D с белой надписью».

  3. 3. «Статус „Доставлен“ — только зелёная точка, без слова».

  4. 4. «Сообщение об ошибке оплаты: подложка error-surface, текст text, значок error».

  5. 5. «Метки сортов в каталоге — „Бразилия“, „Кения“ — хочется разного цвета, а ролей для этого нет».

  6. 6. «В тёмной теме ссылка светлее, чем в светлой, — это разные цвета».

  7. 7. «Разделитель строк в таблице опта нарисован ролью border-field — она потемнее, так заметнее».

  8. 8. «Для графика продаж кофеен нужно шесть различимых цветов линий».

Четыре нарушения, и у каждого есть номер строки. Два места сделаны верно, два — новые случаи, которые система пока не покрывает.

10Задание

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

Часть 1 · Примитивы и роли, 60 минут

  1. Соберите примитивы из того, что уже есть в продукте, и разложите по шкалам: серые, основной цвет, статусы.
  2. Выпишите роли с назначением и значениями для обеих тем — только именами примитивов.
  3. Роль без тёмного значения — первая находка: допишите значение.
Цветовая система · шаблон
ЦВЕТ · ПРОДУКТ: ______________   Версия: __  Дата: __.__.____
Кто отвечает: ______________   Как предложить правку: ______________

1. ПРИМИТИВЫ (шкалы)
   gray:   ___ #______  ___ #______  ___ #______  ...
   ______: ___ #______  ___ #______  ___ #______  ...

2. РОЛИ
   роль          | зачем                      | светлая     | тёмная
   bg            | __________________________ | ___________ | ___________
   text          | __________________________ | ___________ | ___________
   text-muted    | __________________________ | ___________ | ___________
   action        | __________________________ | ___________ | ___________
   on-action     | __________________________ | ___________ | ___________
   error / -surf | __________________________ | ___________ | ___________

3. ТАБЛИЦА КОНТРАСТА (пары ролей)
   пара                  | норма | светлая | тёмная
   text на bg            | 4,5   | ___ : 1 | ___ : 1
   ______ на ______      | ___   | ___ : 1 | ___ : 1

4. СТРОКИ СИСТЕМЫ
   Ц1 ____________________________________________
   Ц2 ____________________________________________

5. НОВЫЙ ЦВЕТ
   роль → проверить, что новая → предложение → значения двух тем
   → пары контраста → Figma и код одновременно → журнал
Значения ролей пишите именами примитивов: так видно, что в систему не пробрался одиночный код цвета.

Часть 2 · Контраст и правило, 30 минут

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

Часть 3 · Чужой макет, 30 минут

  1. Возьмите макет, который делали не вы.
  2. Найдите места, где цвет задан значением, где роль выбрана по виду и где статус передан одним цветом.
  3. Каждую находку запишите с номером строки и видом: нарушение, не ошибка, система не покрывает.
Как понять, что задание выполнено. Новый цвет в продукт другой дизайнер добавил по вашему правилу и без вас.

Что должно получиться

  • Документ системы по шаблону: примитивы, роли в двух темах, таблица пар, строки, порядок добавления.
  • Разбор чужого макета с номерами строк.
  • Через месяц — хотя бы один цвет, добавленный коллегой по правилу.

План работы: 60 минут на примитивы и роли, 30 на контраст и правило, 30 на разбор. Всего — около двух часов.

Частые сложности

  • «В коде сотни прибитых цветов». Не переводите всё сразу. Заведите роли, запретите новые значения в коде и переводите экраны по мере правок.
  • «Бренд-гайд даёт шесть фирменных цветов». Они становятся примитивами. Ролью становится только тот, у кого есть работа на экране.
  • «Разработчики называют по-своему». Договоритесь об одном списке имён ролей до того, как они появятся в коде. Имена в Figma и в CSS должны совпадать.
  • «Роль нужна срочно, ждать некогда». Сделайте исключение с записью в журнале. Порядок добавления для того и нужен, чтобы исключение не превращалось в норму молча.

11Чек-лист и ошибки

Система работает, если

  • Примитивы собраны в шкалы и не выходят на экраны
  • Каждая роль названа по смыслу и описана одной строкой
  • У каждой роли два значения, и оба — примитивы
  • Таблица пар контраста есть для обеих тем и пересчитывается после правок
  • Статусы — слово и значок, цвет помогает
  • Имена ролей в Figma и в коде совпадают
  • Порядок добавления нового цвета записан, и им пользуются
  • У системы есть версия и журнал изменений

Что мешает

  • Код цвета на экране. Не переключается с темой, не находится при смене палитры.
  • Роль по виду. Взяли чужую роль, потому что цвет совпал.
  • Роль без тёмного значения. В тёмной теме показывает цвет светлой.
  • Контраст на глаз. Пары статусов на подложках тихо проваливаются.
  • Цвет без порядка. Оранжевый «на одну акцию» живёт в продукте годами.
  • Роли статусов для категорий. Зелёная метка читается как «успех».

12Три ступени темы

На первой ступени собирали палитру с ролями, на второй — проверяли контраст и собирали тёмную тему. Здесь цвет становится системой, по которой работает вся команда.

СтупеньЧто нужно уметьКак проверить результат
Junior Собрать палитру из ограниченного набора; дать каждому цвету роль; отличать оттенок для поверхности от оттенка для текста; находить дубли; не передавать статус одним цветом Для каждого цвета на экране можно назвать его роль, цветов без роли не осталось
Middle Мерить контраст текста, значков, границ и состояний; собирать тёмную тему на тех же ролях; не гасить неактивное одной прозрачностью В таблице замеров нет пары ниже нормы ни в светлой теме, ни в тёмной
Senior — этот урок Описать цветовую систему: примитивы и роли, обе темы, таблица контраста, правило добавления нового цвета Новый цвет в продукт другой дизайнер добавил по вашему правилу и без вас
Ориентир по этой теме: цвет в продукте описан смыслом, и новый цвет попадает в него по правилу, а не потому что понравился.

Что почитать дальше

  • Design tokens и Color rolesMaterial Design 3 · на английском · бесплатноТри класса токенов — reference, system и component — и где переключается тема: на system-токенах. Во второй статье 26 ролей цвета, которые работают парами с обещанным минимумом 3 : 1, и почему рамка поля и разделитель — разные роли. Вкладка How to use tokens с Adobe XD и DSP устарела.
  • Color, Accents и Design tokensAtlassian Design System · на английском · бесплатноКак устроено имя токена цвета — свойство, роль, выделение, состояние — и десять ролей от brand до danger. Отдельно десять акцентов для категорий без смысла и почему жёлтый среди них не брать. Правило «не бери токен, потому что совпал цвет» — на третьей странице, Design tokens.
  • Color usageGitHub Primer · на английском · бесплатноТри группы токенов — базовые, функциональные, компонентные — и почему базовые не берут напрямую: они не знают про тему. Какие ступени серой шкалы идут на фон, рамки и текст. Роли разведены иначе, чем в уроке: ссылки, фокус и информация у GitHub — одна роль accent, а главная кнопка — зелёная роль success.
  • Color и ThemesIBM Carbon Design System · на английском · бесплатноЧетыре темы — White, Gray 10, Gray 90, Gray 100 — на одних токенах: роль не меняется, меняется значение. Модель слоёв, правила состояний по ступеням палитры и таблица, на сколько ступеней должны отстоять цвета для 4,5 : 1 и 3 : 1. Полный список токенов с ролями — на вкладке Tokens.
  • Design Tokens Format Module 2025.10 и Resolver ModuleW3C Design Tokens Community Group · на английском · бесплатноСтабильный с октября 2025 года JSON-формат для обмена токенами между инструментами: как записать токен, группу и ссылку на другой токен вида {color.brand}. Цвет описан в отдельном модуле Color, светлая и тёмная темы — во второй ссылке, модуле Resolver. Спецификация для авторов инструментов, дизайнеру хватит разделов 5–8.