Что разберём
1Цвет по правилу, а не по вкусу
Пока палитра — привычка одного человека, новый цвет попадает в продукт, потому что понравился.
Через три месяца работы втроём в макетах магазина нашлись:
- оранжевый для баннера акции;
- второй зелёный для статуса «Доставлен»;
- синий кнопки, вписанный в оптовый раздел прямо кодом цвета.
По отдельности каждое решение разумно. Вместе они ломают тёмную тему и контраст, а на вопрос «можно ли взять этот цвет» отвечает только автор палитры.
Цветовая система отвечает на этот вопрос сама. В ней пять частей:
- Примитивы — все цвета продукта со шкалами оттенков.
- Роли — зачем цвет на экране: фон, текст, действие, ошибка.
- Две темы — у каждой роли значение для светлой и для тёмной.
- Таблица контраста — какие пары ролей проходят нормы и насколько.
- Правило добавления — как в систему попадает новый цвет.
2Примитивы и роли
Примитив говорит, какой это цвет. Роль говорит, зачем он.
- Примитив —
blue-600,gray-50. Это значение со своим местом в шкале. Он не знает, где будет стоять, и не меняется от темы. - Роль —
action,surface,error. Она ссылается на примитив, и ссылка зависит от темы: в светлойaction— этоblue-600, в тёмной —blue-300.
«Серый-100» живёт внутри системы. Наружу — в макеты и в код экранов — выходит только роль: «поверхность», «вторичный текст».
Экран говорит, зачем ему цвет, а система решает, какой он.
3Как это устроено в больших системах
Названия разные, устройство общее: слой значений и слой смысла.
| Система | Значения | Смысл | Что говорит о прямом использовании |
|---|---|---|---|
| Material Design 3 | Reference: md.ref.palette.primary40 | System: md.sys.color.primary, on-primary, surface, error, outline. Ещё есть component-токены, они ссылаются на system | Тема переключается на уровне system-токенов: primary в светлой теме — primary40, в тёмной — primary80 |
| Atlassian | Палитра, например Red100 и Red1000 | color.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 Primer | Base: color-scale-pink-5 | Functional: borderColor-sponsors-emphasis, есть ещё уровень компонентов | Базовые токены не учитывают тему и не должны использоваться напрямую ни в коде, ни в макете |
| GOV.UK | govuk-colour("red") | govuk-functional-colour("error"), focus — #ffdd00 | Не копируйте коды цвета. Жёлтый фокуса — только для фокуса |
У Atlassian есть ещё третий тип цвета — акценты: десять цветов без смысла, для меток и категорий, которые выбирает сам человек.
Их нельзя брать там, где у цвета есть значение, например для предупреждения.
Это ответ на вопрос «а какой цвет дать тегу»: не роль статуса, а отдельный набор без смысла.
4Почему примитив нельзя брать напрямую
Экран на примитивах выглядит правильно ровно до смены темы.
Две карточки заказа выглядят одинаково. Левая собрана на ролях, правая — на тех же цветах, вписанных значениями. Включите тёмную тему.
На ролях
На примитивах
| Место | На ролях | На примитивах |
|---|---|---|
| Номер и сумма | Заказ 14,88 : 1 | Заказ 1,07 : 1 |
| Дата | 3 сентября 7,82 : 1 | 3 сентября 3,00 : 1 |
| Статус | Отменён 6,68 : 1 | Отменён 3,18 : 1 |
| Ссылка | Подробнее 7,30 : 1 | Подробнее 3,06 : 1 |
Что ещё ломает прямой примитив
- Смену палитры. Бренд решил сделать синий чуть теплее. Роль
actionменяется в одном месте, а каждый вписанный#1D5FD1надо искать по всем макетам и всему коду. - Смысл. По
#C23030в коде не понять, ошибка это или просто красный заголовок акции. Поerrorпонятно сразу, и его можно найти поиском. - Совпадение цветов. Сегодня у разделителя и у неактивной рамки один серый. Если взять для рамки «роль разделителя, раз цвет тот же», следующая правка разделителя сломает рамку. Atlassian об этом прямо предупреждает.
5Роли продукта в двух темах
Одна таблица на обе темы: у каждой роли два значения, и оба — примитивы.
| Роль | Зачем | Светлая | Тёмная |
|---|---|---|---|
bg | Фон карточек и полей | gray-0 #FFFFFF | gray-950 #15181C |
surface | Фон страницы и шапки | gray-50 #F4F6F8 | gray-850 #1E2227 |
text | Основной текст | gray-900 #1B1F24 | gray-100 #E8EBEE |
text-muted | Вторичный текст, подписи полей | gray-600 #5B6570 | gray-300 #A3ADB8 |
border | Разделители, рамки карточек | gray-150 #DCE1E6 | gray-800 #343A41 |
border-field | Рамка поля ввода | gray-400 #808A94 | gray-500 #6E7883 |
action | Кнопки, ссылки, фокус | blue-600 #1D5FD1 | blue-300 #7AA7F0 |
on-action | Текст на кнопке | gray-0 #FFFFFF | blue-950 #0B1B33 |
error и error-surface | Текст и подложка ошибки | red-600 и red-50 | red-300 и red-900 |
warning и warning-surface | Текст и подложка предупреждения | yellow-700 и yellow-50 | yellow-300 и yellow-900 |
success и success-surface | Текст и подложка успеха | green-700 и green-50 | green-300 и green-900 |
info и info-surface | Текст и подложка сообщения | blue-600 и blue-50 | blue-300 и blue-900 |
У info и action один примитив, но роли разные. Сегодня они совпадают по цвету, завтра info может стать бирюзовым, и кнопки это не заденет.
6Таблица контраста
Контраст считают не для цветов, а для пар ролей, которые встречаются на экране.
Material Design 3 строит роли парами — primary и on-primary, surface и on-surface. И обещает у пар минимум 3 : 1, если их использовать в задуманных сочетаниях.
Этого хватает для крупного текста и значков, но не для обычного текста. Поэтому свою таблицу пар стоит считать самим и держать под обычный текст 4,5 : 1.
| Пара | Норма | Светлая | Тёмная |
|---|---|---|---|
text на bg | 4,5 | Аа 16,56 : 1 | Аа 14,88 : 1 |
text-muted на bg | 4,5 | Аа 5,93 : 1 | Аа 7,82 : 1 |
border-field на bg | 3 | ▬ 3,51 : 1 | ▬ 3,96 : 1 |
action на bg | 4,5 | Аа 5,81 : 1 | Аа 7,30 : 1 |
on-action на action | 4,5 | Аа 5,81 : 1 | Аа 7,06 : 1 |
error на error-surface | 4,5 | Аа 4,84 : 1 | Аа 5,72 : 1 |
warning на warning-surface | 4,5 | Аа 5,40 : 1 | Аа 7,73 : 1 |
success на success-surface | 4,5 | Аа 4,68 : 1 | Аа 6,40 : 1 |
info на info-surface | 4,5 | Аа 5,02 : 1 | Аа 5,85 : 1 |
success и error в светлой теме придётся мерить заново.7Как добавить новый цвет
Новый цвет попадает в продукт по порядку.
Дизайнер промо-страниц хочет оранжевый #FF8A3D для кнопки акции. Вот как это решается по правилу:
- Сначала роль, потом цвет. Зачем оранжевый? «Выделить кнопку акции». Это действие, и роль
actionуже есть. Если нужна вторая, заметная кнопка, это новая роль с именем по смыслу, напримерaction-promo. - Проверить, что роль правда новая. Может быть, хватит существующей: крупный баннер с кнопкой
actionвыделится и без нового цвета. - Предложение коротко: имя роли, где она стоит, почему существующие не подходят, снимок экрана.
- Значения для обеих тем из шкалы. Нет подходящего оттенка — в шкалу добавляется новый примитив.
- Пары контраста для обеих тем. Белый на #FF8A3D даёт 2,34 : 1 — кнопка с такой надписью не проходит. Проходит, например, #B34700: 5,50 : 1. Или оставить яркий оранжевый, но с тёмной надписью: 7,06 : 1. Выбор между ними — за брендом, а не за одним макетом.
- Роль появляется в Figma и в коде одновременно, с датой в журнале изменений системы, и команда о ней узнаёт.
8Роли в коде и в Figma
Работает, когда в макете и в коде одни и те же имена ролей, а примитивы спрятаны внутри системы.
/* Примитивы — только внутри системы, экраны их не видят */
: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. «Кнопка „В корзину“ в оптовом разделе залита #1D5FD1 значением».
Нарушение системыЦ1: цвет на экране — только роль. Значение не переключится в тёмной теме, нужна роль
action.2. «Для кнопки акции взят новый оранжевый #FF8A3D с белой надписью».
Нарушение системыЦ6 и Ц4: цвет пришёл без роли и без порядка добавления, а белый на нём даёт 2,34 : 1.
3. «Статус „Доставлен“ — только зелёная точка, без слова».
Нарушение системыЦ5: статус называют словом. Точка помогает найти его глазами, но сама смысла не несёт.
4. «Сообщение об ошибке оплаты: подложка
error-surface, текстtext, значокerror».Не ошибкаВсе цвета из ролей, пары из таблицы контраста. Основной текст на подложке ошибки читается лучше, чем красный.
5. «Метки сортов в каталоге — „Бразилия“, „Кения“ — хочется разного цвета, а ролей для этого нет».
Система не покрываетНужны цвета без смысла, для категорий. Брать для них роли статусов нельзя: зелёная «Кения» будет читаться как «успех». Это задача для системы — завести набор акцентов, как у Atlassian.
6. «В тёмной теме ссылка светлее, чем в светлой, — это разные цвета».
Не ошибкаЦ3: это одна роль
actionс разными значениями в двух темах. Так и задумано.7. «Разделитель строк в таблице опта нарисован ролью
border-field— она потемнее, так заметнее».Нарушение системыЦ2: роль выбрана по виду. Для разделителей есть
border. Если его не видно в плотной таблице, это повод обсудить рольborder, брать чужую нельзя.8. «Для графика продаж кофеен нужно шесть различимых цветов линий».
Система не покрываетЦвета графиков — отдельная задача: их должно быть много, они должны различаться и без цвета, и в обеих темах. Роли интерфейса для этого не подходят.
10Задание
Опишите цветовую систему продукта: примитивы, роли, обе темы, таблица контраста, правило добавления нового цвета. Прогоните по ней один чужой макет.
Часть 1 · Примитивы и роли, 60 минут
- Соберите примитивы из того, что уже есть в продукте, и разложите по шкалам: серые, основной цвет, статусы.
- Выпишите роли с назначением и значениями для обеих тем — только именами примитивов.
- Роль без тёмного значения — первая находка: допишите значение.
ЦВЕТ · ПРОДУКТ: ______________ Версия: __ Дата: __.__.____ Кто отвечает: ______________ Как предложить правку: ______________ 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 минут
- Выпишите все пары ролей, которые встречаются на экранах, и замерьте их в обеих темах.
- Запишите порядок добавления нового цвета и строки системы с номерами.
Часть 3 · Чужой макет, 30 минут
- Возьмите макет, который делали не вы.
- Найдите места, где цвет задан значением, где роль выбрана по виду и где статус передан одним цветом.
- Каждую находку запишите с номером строки и видом: нарушение, не ошибка, система не покрывает.
Что должно получиться
- Документ системы по шаблону: примитивы, роли в двух темах, таблица пар, строки, порядок добавления.
- Разбор чужого макета с номерами строк.
- Через месяц — хотя бы один цвет, добавленный коллегой по правилу.
План работы: 60 минут на примитивы и роли, 30 на контраст и правило, 30 на разбор. Всего — около двух часов.
Частые сложности
- «В коде сотни прибитых цветов». Не переводите всё сразу. Заведите роли, запретите новые значения в коде и переводите экраны по мере правок.
- «Бренд-гайд даёт шесть фирменных цветов». Они становятся примитивами. Ролью становится только тот, у кого есть работа на экране.
- «Разработчики называют по-своему». Договоритесь об одном списке имён ролей до того, как они появятся в коде. Имена в Figma и в CSS должны совпадать.
- «Роль нужна срочно, ждать некогда». Сделайте исключение с записью в журнале. Порядок добавления для того и нужен, чтобы исключение не превращалось в норму молча.
11Чек-лист и ошибки
Система работает, если
- Примитивы собраны в шкалы и не выходят на экраны
- Каждая роль названа по смыслу и описана одной строкой
- У каждой роли два значения, и оба — примитивы
- Таблица пар контраста есть для обеих тем и пересчитывается после правок
- Статусы — слово и значок, цвет помогает
- Имена ролей в Figma и в коде совпадают
- Порядок добавления нового цвета записан, и им пользуются
- У системы есть версия и журнал изменений
Что мешает
- Код цвета на экране. Не переключается с темой, не находится при смене палитры.
- Роль по виду. Взяли чужую роль, потому что цвет совпал.
- Роль без тёмного значения. В тёмной теме показывает цвет светлой.
- Контраст на глаз. Пары статусов на подложках тихо проваливаются.
- Цвет без порядка. Оранжевый «на одну акцию» живёт в продукте годами.
- Роли статусов для категорий. Зелёная метка читается как «успех».
12Три ступени темы
На первой ступени собирали палитру с ролями, на второй — проверяли контраст и собирали тёмную тему. Здесь цвет становится системой, по которой работает вся команда.
| Ступень | Что нужно уметь | Как проверить результат |
|---|---|---|
| Junior | Собрать палитру из ограниченного набора; дать каждому цвету роль; отличать оттенок для поверхности от оттенка для текста; находить дубли; не передавать статус одним цветом | Для каждого цвета на экране можно назвать его роль, цветов без роли не осталось |
| Middle | Мерить контраст текста, значков, границ и состояний; собирать тёмную тему на тех же ролях; не гасить неактивное одной прозрачностью | В таблице замеров нет пары ниже нормы ни в светлой теме, ни в тёмной |
| Senior — этот урок | Описать цветовую систему: примитивы и роли, обе темы, таблица контраста, правило добавления нового цвета | Новый цвет в продукт другой дизайнер добавил по вашему правилу и без вас |
Что почитать дальше
- Design tokens и Color rolesТри класса токенов — reference, system и component — и где переключается тема: на system-токенах. Во второй статье 26 ролей цвета, которые работают парами с обещанным минимумом 3 : 1, и почему рамка поля и разделитель — разные роли. Вкладка How to use tokens с Adobe XD и DSP устарела.
- Color, Accents и Design tokensКак устроено имя токена цвета — свойство, роль, выделение, состояние — и десять ролей от brand до danger. Отдельно десять акцентов для категорий без смысла и почему жёлтый среди них не брать. Правило «не бери токен, потому что совпал цвет» — на третьей странице, Design tokens.
- Color usageТри группы токенов — базовые, функциональные, компонентные — и почему базовые не берут напрямую: они не знают про тему. Какие ступени серой шкалы идут на фон, рамки и текст. Роли разведены иначе, чем в уроке: ссылки, фокус и информация у GitHub — одна роль accent, а главная кнопка — зелёная роль success.
- Color и ThemesЧетыре темы — White, Gray 10, Gray 90, Gray 100 — на одних токенах: роль не меняется, меняется значение. Модель слоёв, правила состояний по ступеням палитры и таблица, на сколько ступеней должны отстоять цвета для 4,5 : 1 и 3 : 1. Полный список токенов с ролями — на вкладке Tokens.
- Design Tokens Format Module 2025.10 и Resolver ModuleСтабильный с октября 2025 года JSON-формат для обмена токенами между инструментами: как записать токен, группу и ссылку на другой токен вида {color.brand}. Цвет описан в отдельном модуле Color, светлая и тёмная темы — во второй ссылке, модуле Resolver. Спецификация для авторов инструментов, дизайнеру хватит разделов 5–8.