Уроки

Семантические цветовые системы: именуйте цвета по назначению, а не по оттенку

8 мин чтения

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

Это не косметическое различие. Оно меняет подход команд к принятию решений, реакцию продуктов на смену темы и степень доступности интерфейса в разных режимах.


Почему семантическое именование превосходит именование по оттенку

Проблема именования на практике

Большинство команд начинают с именования по оттенку. Руководство по стилю говорит: «Основной: blue-600. Дополнительный: gray-700. Ошибка: red-500.» Это выглядит чисто — и какое-то время является таковым. Проблема возникает, когда:

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

При именовании по оттенку каждый ребрендинг — это операция поиска и замены по всей кодовой базе. При семантическом именовании вы меняете значение --color-brand-primary в одном файле, и каждый использующий его компонент обновляется автоматически.

Семантические имена передают намерение

Переменная с именем --color-interactive-default сообщает о своей задаче: этот цвет маркирует элементы, с которыми пользователи могут взаимодействовать. Переменная blue-500 не говорит ничего, кроме того, как она выглядит. Когда разработчик видит color: var(--color-text-danger) в таблице стилей, он немедленно понимает: это текст в состоянии опасности. Видя color: #EF4444, ему нужно сверяться со спецификацией, чтобы понять, почему выбран именно этот красный.

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

Тёмный режим по своей природе семантичен

Тёмный режим — наиболее очевидный аргумент в пользу семантических систем. В светлом режиме --color-surface-default может быть #FFFFFF. В тёмном — #1E1E1E. Семантическое имя остаётся постоянным; меняется только значение. Если система построена вокруг имён оттенков, «белый» и «почти чёрный» не могут разделять одну семантическую роль — повсюду потребовалась бы условная логика вместо единой замены токена.


Примитивные и семантические токены цвета

Хорошо структурированная система цветовых токенов имеет как минимум два слоя: примитивные токены и семантические токены.

Примитивные токены: цветовая палитра

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

/* Примитивные токены — полная цветовая палитра */
--color-blue-50: #EFF6FF;
--color-blue-100: #DBEAFE;
--color-blue-200: #BFDBFE;
--color-blue-300: #93C5FD;
--color-blue-400: #60A5FA;
--color-blue-500: #3B82F6;
--color-blue-600: #2563EB;
--color-blue-700: #1D4ED8;
--color-blue-800: #1E40AF;
--color-blue-900: #1E3A8A;
--color-blue-950: #172554;

--color-green-50: #F0FDF4;
--color-green-500: #22C55E;
--color-green-700: #15803D;

--color-red-50: #FFF1F2;
--color-red-500: #EF4444;
--color-red-700: #B91C1C;

--color-yellow-50: #FEFCE8;
--color-yellow-500: #EAB308;
--color-yellow-700: #A16207;

--color-neutral-0: #FFFFFF;
--color-neutral-50: #F8FAFC;
--color-neutral-100: #F1F5F9;
--color-neutral-200: #E2E8F0;
--color-neutral-900: #0F172A;
--color-neutral-950: #020617;

Примитивные токены никогда не используются напрямую в коде компонентов. Они существуют исключительно как исходные значения для семантических токенов.

Семантические токены: цвета с назначением

Семантические токены ссылаются на примитивные токены и назначают им роли:

/* Семантические токены — назначение каждого цвета */
:root {
  /* Бренд */
  --color-brand-primary: var(--color-blue-600);
  --color-brand-primary-hover: var(--color-blue-700);
  --color-brand-primary-subtle: var(--color-blue-50);

  /* Текст */
  --color-text-default: var(--color-neutral-900);
  --color-text-muted: var(--color-neutral-500);
  --color-text-inverse: var(--color-neutral-0);
  --color-text-danger: var(--color-red-700);
  --color-text-success: var(--color-green-700);
  --color-text-warning: var(--color-yellow-700);
  --color-text-info: var(--color-blue-700);

  /* Поверхности */
  --color-surface-default: var(--color-neutral-0);
  --color-surface-subtle: var(--color-neutral-50);
  --color-surface-raised: var(--color-neutral-0);
  --color-surface-overlay: var(--color-neutral-50);

  /* Границы */
  --color-border-default: var(--color-neutral-200);
  --color-border-focus: var(--color-blue-600);
  --color-border-danger: var(--color-red-500);

  /* Состояния обратной связи */
  --color-feedback-danger-surface: var(--color-red-50);
  --color-feedback-danger-border: var(--color-red-500);
  --color-feedback-danger-text: var(--color-red-700);
  --color-feedback-success-surface: var(--color-green-50);
  --color-feedback-warning-surface: var(--color-yellow-50);
  --color-feedback-info-surface: var(--color-blue-50);
}

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


Паттерны цветов для успеха, предупреждения, ошибки и информации

Цвета обратной связи — наиболее распространённая отправная точка для семантических систем, поскольку их назначение очевидно. Никто не называет состояние ошибки #EF4444 из-за его оттенка — его выбирают, потому что он сигнализирует об опасности. Сделать это намерение явным в имени токена — небольшой шаг с большой отдачей.

Четыре категории обратной связи

Успех передаёт положительные результаты: форма отправлена, платёж обработан, файл загружен. Цвета успеха, как правило, зелёные, хотя варианты с бирюзовым и изумрудным тоже работают. Критическое требование — чтобы цвет воспринимался как позитивный в разных культурах: обычно это означает избегание жёлто-зелёных, выглядящих болезненно, и предпочтение чётких, насыщенных зелёных.

Полезное трио для успеха: - Поверхность: #F0FDF4 — очень светлый зелёный фон для баннеров успеха - Граница: #22C55E — видимая зелёная граница контейнера - Текст: #15803D — более тёмный зелёный для читаемого текста внутри

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

Трио предупреждения, проходящее WCAG AA: - Поверхность: #FEFCE8 — бледный жёлтый фон - Граница: #EAB308 — видимая жёлтая граница - Текст: #713F12 — тёмный янтарный текст (достаточно тёмный для достаточного контраста)

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

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

Доступная реализация обратной связи

<!-- Доступное состояние ошибки: цвет + иконка + текст (три канала) -->
<div class="alert" role="alert" aria-live="polite">
  <svg aria-hidden="true" class="alert-icon"><!-- иконка ошибки --></svg>
  <p class="alert-message">
    <strong>Платёж не прошёл.</strong>
    Карта отклонена. Проверьте данные карты и попробуйте снова.
  </p>
</div>
.alert {
  display: flex;
  gap: 0.75rem;
  padding: 1rem;
  border-radius: 0.375rem;
  border-width: 1px;
  border-style: solid;
  background-color: var(--color-feedback-danger-surface);
  border-color: var(--color-feedback-danger-border);
  color: var(--color-feedback-danger-text);
}

Обратите внимание на трёхканальный подход: цвет (красная поверхность и граница), форма (иконка ошибки) и текст (описательное сообщение). Это удовлетворяет WCAG 1.4.1 — информация не передаётся только цветом.


Архитектура токенов: глобальные → алиасные → компонентные

Дизайн-системы корпоративного масштаба добавляют третий слой между примитивными и семантическими токенами: компонентные токены. Эта трёхуровневая архитектура часто называется «глобальные → алиасные → компонентные».

Уровень 1: Глобальные токены (примитивные)

Исходные значения. Числа, цвета, размеры шрифтов. Без мнений. Это атомы:

/* Глобальные / Примитивные */
--global-color-red-500: #EF4444;
--global-color-red-700: #B91C1C;
--global-space-4: 1rem;
--global-radius-md: 0.375rem;

Уровень 2: Алиасные токены (семантические)

Назначение ролей. Ссылаются на глобальные токены и дают им роли. Здесь живёт семантическое именование:

/* Алиасные / Семантические */
--alias-color-feedback-error-surface: var(--global-color-red-50);
--alias-color-feedback-error-border: var(--global-color-red-500);
--alias-color-feedback-error-text: var(--global-color-red-700);

Уровень 3: Компонентные токены

Назначения на уровне компонентов, ссылающиеся на алиасные токены. Они позволяют настраивать отдельный компонент без влияния на весь семантический слой:

/* Токены уровня компонента */
--input-border-color-error: var(--alias-color-feedback-error-border);
--input-text-color-error: var(--alias-color-feedback-error-text);
--alert-surface-color-error: var(--alias-color-feedback-error-surface);

Таблица стилей компонента использует только его собственные компонентные токены:

.input--error {
  border-color: var(--input-border-color-error);
}

.input--error-message {
  color: var(--input-text-color-error);
}

Это означает: если вы хотите настроить границу ошибки именно для инпутов — возможно, сделав её чуть темнее для лучшей видимости в конкретном макете формы, — вы меняете --input-border-color-error в файле токенов компонента, не трогая алиасный слой вообще.


Реализация в CSS-переменных и Tailwind

CSS-переменные

Полная CSS-реализация использует каскад, делающий тёмный режим элегантным:

:root {
  /* Значения по умолчанию для светлого режима */
  --color-surface-default: #FFFFFF;
  --color-text-default: #0F172A;
  --color-brand-primary: #2563EB;
  --color-feedback-danger-surface: #FFF1F2;
  --color-feedback-danger-border: #EF4444;
  --color-feedback-danger-text: #B91C1C;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Переопределения для тёмного режима — те же имена токенов, другие значения */
    --color-surface-default: #0F172A;
    --color-text-default: #F8FAFC;
    --color-brand-primary: #60A5FA;
    --color-feedback-danger-surface: #450A0A;
    --color-feedback-danger-border: #F87171;
    --color-feedback-danger-text: #FCA5A5;
  }
}

Каждый компонент продолжает ссылаться на те же имена токенов. Тёмный режим полностью управляется в объявлении :root — никакой условной логики в коде компонентов.

Интеграция с Tailwind CSS

CSS-первая конфигурация Tailwind v4 делает интеграцию семантических цветов простой. Определите семантические токены во входном файле стилей и ссылайтесь на них во всех утилитах Tailwind:

/* styles.css — Tailwind v4 */
@import "tailwindcss";

@theme {
  --color-brand-primary: #2563EB;
  --color-brand-primary-hover: #1D4ED8;
  --color-brand-primary-subtle: #EFF6FF;

  --color-text-default: #0F172A;
  --color-text-muted: #64748B;
  --color-text-danger: #B91C1C;

  --color-surface-default: #FFFFFF;
  --color-surface-subtle: #F8FAFC;

  --color-feedback-danger-surface: #FFF1F2;
  --color-feedback-danger-border: #EF4444;
}

В Tailwind v3 расширьте конфигурацию:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          primary: 'var(--color-brand-primary)',
          'primary-hover': 'var(--color-brand-primary-hover)',
          subtle: 'var(--color-brand-primary-subtle)',
        },
        feedback: {
          'danger-surface': 'var(--color-feedback-danger-surface)',
          'danger-border': 'var(--color-feedback-danger-border)',
          'danger-text': 'var(--color-feedback-danger-text)',
        },
      },
    },
  },
}

Теперь Tailwind генерирует утилиты bg-brand-primary, text-feedback-danger-text и border-feedback-danger-border. Разметка шаблонов никогда не содержит исходного hex-значения:

<div class="rounded-md border border-feedback-danger-border bg-feedback-danger-surface p-4">
  <p class="text-sm text-feedback-danger-text font-medium">
    Это поле обязательно для заполнения.
  </p>
</div>

Дизайн-токены в JSON для мультиплатформенных систем

Если команда работает на web, iOS и Android, данные токенов часто хранятся в платформенно-нейтральном JSON-формате и преобразуются в платформенно-специфические форматы (CSS-переменные, Swift-константы, Kotlin-константы) инструментом вроде Style Dictionary:

{
  "color": {
    "feedback": {
      "danger": {
        "surface": {
          "$value": "#FFF1F2",
          "$type": "color",
          "$description": "Фон для состояний опасности/ошибки"
        },
        "border": {
          "$value": "#EF4444",
          "$type": "color",
          "$description": "Цвет границы для состояний опасности/ошибки"
        },
        "text": {
          "$value": "#B91C1C",
          "$type": "color",
          "$description": "Цвет текста внутри состояний опасности/ошибки. Проходит 4.5:1 на белом."
        }
      }
    }
  }
}

Style Dictionary читает этот JSON и генерирует CSS, Swift, Kotlin и любой другой формат, требуемый конвейером сборки. Команды, использующие Figma, могут синхронизировать JSON-токены напрямую с дизайн-файлами через плагин Tokens Studio, поддерживая дизайн и код в синхронизации.


Распространённые ошибки

Смешение слоёв именования в компонентах. Если компонент ссылается одновременно на --color-blue-600 (примитивный) и --color-brand-primary (семантический), дисциплина нарушается. Введите правило линтера или соглашение о код-ревью: код компонента может ссылаться только на семантические или компонентные токены.

Слишком много семантических ролей. Некоторые команды создают семантический токен для каждого контекста — --color-sidebar-background, --color-modal-background, --color-drawer-background — когда все три сопоставляются с одной поверхностью. Консолидируйте там, где цвета действительно разделяют назначение. Полезный тест: если два семантических токена всегда имеют одно и то же значение и всегда меняются вместе — объедините их.

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

Именование для сегодняшнего дизайна, а не для завтрашнего. Если сегодня --color-brand-primary — синий, будущий ребрендинг в фиолетовый по-прежнему сработает — семантическое имя остаётся допустимым. Но если вы назвали его --color-brand-blue, имя будет неверным в момент смены бренда. Держите семантические имена свободными от ссылок на оттенок везде, где это возможно.


Резюме

Семантические цветовые системы — это единовременная инвестиция, приносящая дивиденды каждый раз, когда продукт меняется. Ключевые принципы:

  1. Называйте цвета по назначению, а не по внешнему виду. --color-text-danger вместо --color-red.
  2. Разделяйте примитивные токены (вся палитра) и семантические токены (назначение ролей).
  3. Ссылайтесь только на семантические токены в коде компонентов. Никогда не обращайтесь к примитивам напрямую.
  4. Управляйте тёмным режимом и темизацией на семантическом слое — компоненты остаются неизменными.
  5. Для крупных команд добавьте слой компонентных токенов для настройки на уровне компонента без нарушения семантического слоя.

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

Похожие цвета

Похожие бренды

Похожие инструменты