Построение цветовой системы для дизайн-системы
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
Стихийные цветовые решения накапливаются в хаос. Фирменный синий, заданный как #3B82F6 в одном компоненте, #2563EB — в другом и rgba(59, 130, 246, 0.8) — в третьем, при том что все они якобы «один и тот же» синий, порождают визуальную непоследовательность, разрушающую согласованность бренда, делающую тёмный режим нереализуемым и превращающую каждое изменение цвета в археологические раскопки по кодовой базе.
Цветовая система решает эту проблему, устанавливая единый источник истины для каждого цвета в интерфейсе. Она организована в два слоя: примитивная палитра из всех доступных цветовых значений и слой семантических токенов, отображающий примитивы на их предназначенные роли. Такое разделение позволяет реализовать тёмный режим, режим высокой контрастности и ребрендинг путём изменения назначений семантических токенов — без единого изменения в коде компонентов.
В этом руководстве описано построение полной, готовой к продакшену цветовой системы с нуля: выбор примитивной палитры, генерация шкал оттенков, определение семантических токенов, их реализация в CSS и Tailwind, а также организация документации и тестирования.
Почему систематизированные цвета важны
Аргумент в пользу систематизированных цветов — не эстетический, а операционный. Вот что ломается без системы:
Непоследовательность нарастает со временем: в начале проекта дизайнеры и разработчики принимают стихийные цветовые решения, которые «выглядят правильно». Спустя два года кодовая база накапливает 40 различных оттенков синего, которые все должны быть одним и тем же синим. Никто не знает, какой из них правильный.
Тёмный режим требует семантического отображения: тёмный режим не сработает, если цвета закодированы по их визуальному значению. color: #1E293B — тёмный цвет, хорошо смотрящийся как текст на светлом фоне. В тёмном режиме нужно другое значение. Если #1E293B захардкожен в 80 компонентах, вам придётся обновить 80 мест — или создать сложную систему CSS-переопределений, порождающую новый технический долг. Семантические токены устраняют эту проблему.
Смена темы обходится дорого: ребрендинг или изменение основного цвета должны быть единственным изменением. При семантической системе вы меняете --color-primary-600 в файле токенов — и каждая кнопка, ссылка, кольцо фокуса и граница, ссылающаяся на него, обновляются автоматически.
Аудиты доступности молча проваливаются: без системы проверка того, что весь текст соответствует требованиям WCAG к контрасту, требует ручного аудита каждой комбинации текст/фон. Семантические токены позволяют определить правила («text-primary на surface-primary должен пройти AA») и проверять их программно.
Соглашения об именовании: примитивные и семантические токены
Хорошо структурированная цветовая система имеет два уровня именования, выполняющих разные функции.
Примитивные цветовые токены
Примитивные токены — это сырые цветовые значения, названные по их позиции в шкале. Они не предполагают конкретного использования — это просто палитра доступных цветов:
blue-50 → #EFF6FF
blue-100 → #DBEAFE
blue-200 → #BFDBFE
blue-300 → #93C5FD
blue-400 → #60A5FA
blue-500 → #3B82F6
blue-600 → #2563EB
blue-700 → #1D4ED8
blue-800 → #1E40AF
blue-900 → #1E3A8A
blue-950 → #172554
Имена примитивных токенов отвечают на вопрос: «Что это за цвет?» Они не отвечают: «Для чего этот цвет?»
Семантические цветовые токены
Семантические токены ссылаются на примитивные и называют цвета по их назначению:
color-action-primary → blue-600
color-action-primary-hover → blue-700
color-action-primary-text → white-0
color-surface-default → neutral-50 (светлый режим) / neutral-900 (тёмный режим)
color-surface-elevated → white-0 (светлый режим) / neutral-800 (тёмный режим)
color-text-primary → neutral-900 (светлый режим) / neutral-50 (тёмный режим)
color-text-secondary → neutral-600 (светлый режим) / neutral-400 (тёмный режим)
color-text-disabled → neutral-400 (светлый режим) / neutral-600 (тёмный режим)
color-border-default → neutral-200 (светлый режим) / neutral-700 (тёмный режим)
color-border-focused → blue-500
Имена семантических токенов отвечают на вопрос: «Для чего этот цвет?» Они не задают конкретное значение — они ссылаются на примитив.
Это разделение — ключевое структурное решение. Компоненты ссылаются на семантические токены, а не на примитивы. Определяя тёмный режим, вы переназначаете семантические токены на другие примитивы — код компонентов не меняется.
Схемы именования в реальных системах
Разные дизайн-системы используют разные соглашения для семантического слоя:
| Система | Пример семантического имени | Соглашение |
|---|---|---|
| Material Design | sys.color.primary |
Иерархия ролей через точку |
| Apple HIG | label, secondaryLabel |
Простые имена ролей |
| Radix UI | --accent-9, --gray-11 |
Имена на основе шкалы |
| Primer (GitHub) | fgColor.default, bgColor.default |
Роль + вариант |
Соглашение важно меньше, чем последовательность. Выберите одну схему и применяйте её повсеместно.
Генерация шкалы оттенков (50–950)
Шкала оттенков — упорядоченный ряд цветовых значений от почти белого до почти чёрного в рамках одного цветового семейства. Tailwind CSS популяризировал шкалу 50–950; Material Design использует 100–900. Конкретные числа менее важны, чем наличие достаточного количества шагов для покрытия всего диапазона: очень светлые тинты для фонов, средние значения для интерактивных элементов и глубокие оттенки для текста.
Признаки качественной шкалы оттенков
Пригодная шкала оттенков обладает двумя свойствами:
- Достаточное количество воспринимаемых шагов: соседние оттенки должны быть визуально различимы — не настолько близкими, чтобы разница была незаметна, и не настолько далёкими, чтобы возникали пробелы в покрытии.
- Последовательная прогрессия яркости: значения яркости должны предсказуемо возрастать от 950 (темнее) к 50 (светлее) без неожиданных скачков или инверсий.
Типичный сбой вручную подобранных шкал — нерегулярный шаг яркости. Шкала, в которой оттенки 300, 400 и 500 перцептивно схожи, а от 500 до 700 происходит резкий прыжок, — непригодна: вы всегда будете тянуться к 500, когда нужно что-то в середине диапазона.
Генерация оттенков в OKLCH
OKLCH (равноконтрастное пространство с характеристиками Lightness-Chroma-Hue) — лучшее цветовое пространство для генерации оттенков, поскольку его канал яркости перцептивно равномерен: равные числовые шаги дают равные воспринимаемые шаги. Если распределить значения яркости от L=0,15 (очень тёмный) до L=0,98 (очень светлый) с равными интервалами, полученные оттенки будут выглядеть равномерно распределёнными.
Для генерации 11-шаговой шкалы оттенков в OKLCH:
- Выберите базовый оттенок и насыщенность: например, H=250 (синий), C=0,20.
- Разместите значения яркости равномерно: L = 0,15, 0,22, 0,30, 0,38, 0,47, 0,55, 0,63, 0,72, 0,82, 0,92, 0,97.
- При желании немного варьируйте насыщенность: снижайте её на крайних значениях (очень светлый и очень тёмный), где насыщенные цвета выглядят неуместно.
- Конвертируйте значения OKLCH в hex для реализации.
Практическая шкала OKLCH-to-hex для синего оттенка:
Оттенок | OKLCH | Hex
--------+--------------------------+--------
50 | oklch(0.97 0.04 250) | #EFF6FF
100 | oklch(0.92 0.07 250) | #DBEAFE
200 | oklch(0.84 0.11 250) | #BFDBFE
300 | oklch(0.74 0.16 250) | #93C5FD
400 | oklch(0.65 0.19 250) | #60A5FA
500 | oklch(0.55 0.22 250) | #3B82F6
600 | oklch(0.47 0.22 250) | #2563EB
700 | oklch(0.40 0.20 250) | #1D4ED8
800 | oklch(0.32 0.18 250) | #1E40AF
900 | oklch(0.24 0.14 250) | #1E3A8A
950 | oklch(0.17 0.10 250) | #172554
Используйте Генератор оттенков ColorFYI для создания шкал оттенков в стиле Tailwind из любого базового цвета, а Конвертер цветов — для проверки каждого оттенка в пространстве OKLCH и верификации прогрессии яркости.
Сколько шагов нужно?
Для большинства дизайн-систем оптимальный диапазон — 9–11 шагов:
- 50 и 950: крайние значения для фоновых поверхностей и самого тёмного текста/границ.
- 100–200: светлые тонированные фоны (фоны предупреждений, состояния наведения на белых поверхностях).
- 300–400: цвета иконок, текст-заполнитель, отключённые состояния.
- 500: часто «чистый» фирменный цвет — тот, который задан в стандартах фирменного стиля.
- 600–700: заливки интерактивных элементов (кнопок, ссылок), где требуется контраст с белым.
- 800–900: текст и текст на цветном фоне.
Генерируйте оттенки для каждого цветового семейства в палитре: основного фирменного цвета, нейтральной серой серии и акцентных цветов (красный/ошибка, зелёный/успех, жёлтый/предупреждение и т. д.).
Семантические цветовые токены
После установки примитивов определите семантические токены, отображающие примитивы на роли. Детализация этого отображения определяет гибкость системы.
Цвета поверхностей
Токены поверхностей определяют фоновые слои интерфейса:
:root {
--color-surface-base: var(--neutral-50); /* Фон страницы */
--color-surface-elevated: var(--white); /* Фон карточек, листов */
--color-surface-overlay: var(--white); /* Фон модальных окон, поповеров */
--color-surface-sunken: var(--neutral-100); /* Внутренние поля, блоки кода */
}
[data-theme="dark"] {
--color-surface-base: var(--neutral-950);
--color-surface-elevated: var(--neutral-900);
--color-surface-overlay: var(--neutral-800);
--color-surface-sunken: var(--neutral-950);
}
Цвета текста
Текстовые токены охватывают все текстовые роли в иерархии:
:root {
--color-text-primary: var(--neutral-900); /* Основной текст, заголовки */
--color-text-secondary: var(--neutral-600); /* Подписи, метки */
--color-text-tertiary: var(--neutral-400); /* Заполнитель, подсказка */
--color-text-disabled: var(--neutral-300); /* Отключённое состояние */
--color-text-inverse: var(--white); /* Текст на тёмном/цветном фоне */
--color-text-link: var(--blue-600);
--color-text-link-hover: var(--blue-700);
}
[data-theme="dark"] {
--color-text-primary: var(--neutral-50);
--color-text-secondary: var(--neutral-400);
--color-text-tertiary: var(--neutral-600);
--color-text-disabled: var(--neutral-700);
--color-text-link: var(--blue-400);
--color-text-link-hover: var(--blue-300);
}
Интерактивные / действия
Токены действий охватывают каждое состояние интерактивных элементов:
:root {
/* Основное действие */
--color-action-primary-bg: var(--blue-600);
--color-action-primary-bg-hover: var(--blue-700);
--color-action-primary-bg-active: var(--blue-800);
--color-action-primary-text: var(--white);
/* Вторичное / призрачное действие */
--color-action-secondary-border: var(--blue-600);
--color-action-secondary-text: var(--blue-600);
--color-action-secondary-bg-hover: var(--blue-50);
/* Деструктивное действие */
--color-action-danger-bg: var(--red-600);
--color-action-danger-bg-hover: var(--red-700);
--color-action-danger-text: var(--white);
}
Цвета обратной связи / статуса
Статусные токены отображают функциональные цвета на их семантическое значение:
:root {
/* Успех */
--color-status-success-bg: var(--green-50);
--color-status-success-border: var(--green-200);
--color-status-success-text: var(--green-700);
--color-status-success-icon: var(--green-500);
/* Предупреждение */
--color-status-warning-bg: var(--yellow-50);
--color-status-warning-border: var(--yellow-200);
--color-status-warning-text: var(--yellow-800);
--color-status-warning-icon: var(--yellow-500);
/* Ошибка */
--color-status-error-bg: var(--red-50);
--color-status-error-border: var(--red-200);
--color-status-error-text: var(--red-700);
--color-status-error-icon: var(--red-500);
/* Информация */
--color-status-info-bg: var(--blue-50);
--color-status-info-border: var(--blue-200);
--color-status-info-text: var(--blue-700);
--color-status-info-icon: var(--blue-500);
}
Цвета границ
:root {
--color-border-default: var(--neutral-200);
--color-border-strong: var(--neutral-400);
--color-border-focused: var(--blue-500);
--color-border-error: var(--red-500);
--color-border-disabled: var(--neutral-200);
}
[data-theme="dark"] {
--color-border-default: var(--neutral-700);
--color-border-strong: var(--neutral-500);
--color-border-focused: var(--blue-400);
--color-border-error: var(--red-400);
--color-border-disabled: var(--neutral-700);
}
Реализация в CSS
CSS-реализация использует кастомные свойства, определённые на уровне :root (или на селекторах [data-theme] для тематизации). Компоненты ссылаются на семантические токены, никогда напрямую на примитивы.
Структура файлов
design-system/
├── tokens/
│ ├── primitives.css # Все примитивные цветовые значения
│ ├── semantic.css # Семантический токен → отображение примитива
│ └── dark.css # Переопределения семантики для тёмного режима
└── components/
├── button.css
├── card.css
└── ...
primitives.css
:root {
/* Нейтральная шкала */
--neutral-50: #F8FAFC;
--neutral-100: #F1F5F9;
--neutral-200: #E2E8F0;
--neutral-300: #CBD5E1;
--neutral-400: #94A3B8;
--neutral-500: #64748B;
--neutral-600: #475569;
--neutral-700: #334155;
--neutral-800: #1E293B;
--neutral-900: #0F172A;
--neutral-950: #020617;
/* Синяя шкала */
--blue-50: #EFF6FF;
--blue-100: #DBEAFE;
--blue-200: #BFDBFE;
--blue-300: #93C5FD;
--blue-400: #60A5FA;
--blue-500: #3B82F6;
--blue-600: #2563EB;
--blue-700: #1D4ED8;
--blue-800: #1E40AF;
--blue-900: #1E3A8A;
--blue-950: #172554;
/* Белый и чёрный */
--white: #FFFFFF;
--black: #000000;
}
Использование в компонентах
Компоненты ссылаются только на семантические токены:
.button-primary {
background-color: var(--color-action-primary-bg);
color: var(--color-action-primary-text);
border: none;
}
.button-primary:hover {
background-color: var(--color-action-primary-bg-hover);
}
.button-primary:active {
background-color: var(--color-action-primary-bg-active);
}
.button-primary:disabled {
background-color: var(--neutral-200); /* Исключение: отключённое состояние часто использует примитивы */
color: var(--color-text-disabled);
}
При смене темы со светлой на тёмную --color-action-primary-bg автоматически разрешается в другое значение — никаких переопределений тёмного режима для конкретных кнопок не требуется.
Реализация в Tailwind CSS
Tailwind v3 и v4 поддерживают интеграцию дизайн-токенов через конфигурационный файл и подход с CSS-переменными.
Конфигурация Tailwind v3
Определите цветовую шкалу в tailwind.config.js и привяжите к CSS-кастомным свойствам:
// tailwind.config.js
const colors = require("tailwindcss/colors");
module.exports = {
theme: {
colors: {
transparent: "transparent",
current: "currentColor",
white: "#ffffff",
black: "#000000",
// Примитивные шкалы
neutral: {
50: "#F8FAFC",
100: "#F1F5F9",
200: "#E2E8F0",
300: "#CBD5E1",
400: "#94A3B8",
500: "#64748B",
600: "#475569",
700: "#334155",
800: "#1E293B",
900: "#0F172A",
950: "#020617",
},
blue: {
50: "#EFF6FF",
100: "#DBEAFE",
200: "#BFDBFE",
300: "#93C5FD",
400: "#60A5FA",
500: "#3B82F6",
600: "#2563EB",
700: "#1D4ED8",
800: "#1E40AF",
900: "#1E3A8A",
950: "#172554",
},
// Семантические токены через CSS-переменные
"action-primary": "var(--color-action-primary-bg)",
"action-primary-hover": "var(--color-action-primary-bg-hover)",
"surface": "var(--color-surface-base)",
"surface-elevated": "var(--color-surface-elevated)",
"text-primary": "var(--color-text-primary)",
"text-secondary": "var(--color-text-secondary)",
"border-default": "var(--color-border-default)",
},
},
};
С такой настройкой вы можете писать bg-action-primary, и это разрешится в правильное значение семантического токена, который в свою очередь разрешается в правильный примитив для текущей темы.
CSS-ориентированная конфигурация Tailwind v4
Tailwind v4 заменяет JavaScript-конфиг CSS-ориентированным подходом с использованием @theme:
@import "tailwindcss";
@theme {
/* Примитивные цвета */
--color-blue-500: #3B82F6;
--color-blue-600: #2563EB;
--color-blue-700: #1D4ED8;
--color-neutral-50: #F8FAFC;
--color-neutral-900: #0F172A;
/* Семантические токены */
--color-surface: var(--color-surface-base);
--color-text: var(--color-text-primary);
--color-primary: var(--color-action-primary-bg);
}
/* Семантические определения */
:root {
--color-surface-base: var(--color-neutral-50);
--color-text-primary: var(--color-neutral-900);
--color-action-primary-bg: var(--color-blue-600);
}
[data-theme="dark"] {
--color-surface-base: oklch(0.145 0 0);
--color-text-primary: var(--color-neutral-50);
--color-action-primary-bg: var(--color-blue-500);
}
Тестирование и документация
Цветовая система полезна только тогда, когда она понята и надёжно реализована каждым членом команды. Тестирование выявляет регрессии; документация делает систему доступной для изучения.
Тестирование контраста
Каждый текстовый токен в паре с предполагаемым фоновым токеном поверхности должен быть программно проверен на соответствие требованиям WCAG 2.1 AA (4,5:1 для обычного текста, 3:1 для крупного). Напишите тесты, которые:
- Разрешают текущие значения CSS-кастомных свойств (для обеих тем — светлой и тёмной).
- Вычисляют коэффициент контраста WCAG для каждой пары текст/поверхность.
- Утверждают, что коэффициент соответствует требуемому порогу.
import { getContrastRatio } from "some-color-utility";
const tokens = {
light: {
textPrimary: "#0F172A",
surfaceBase: "#F8FAFC",
},
dark: {
textPrimary: "#F8FAFC",
surfaceBase: "#020617",
}
};
describe("Color system contrast", () => {
test("text-primary on surface-base passes AA in light mode", () => {
const ratio = getContrastRatio(tokens.light.textPrimary, tokens.light.surfaceBase);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});
test("text-primary on surface-base passes AA in dark mode", () => {
const ratio = getContrastRatio(tokens.dark.textPrimary, tokens.dark.surfaceBase);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});
});
Используйте Проверку контраста ColorFYI для ручной верификации отдельных пар токенов при их определении, до написания автоматических тестов.
Документация цветовых токенов
Документируйте каждый семантический токен с указанием: - Имени: точного имени CSS-переменной. - Значения (светлый режим): примитива, на который он ссылается в светлом режиме. - Значения (тёмный режим): примитива в тёмном режиме. - Использования: где этот токен следует применять. - Антипаттернов: где его не следует применять.
| Токен | Светлый | Тёмный | Использование |
|------------------------------|----------------|----------------|-----------------------------------|
| --color-text-primary | neutral-900 | neutral-50 | Основной текст, заголовки |
| --color-text-secondary | neutral-600 | neutral-400 | Подписи, метаданные, метки |
| --color-action-primary-bg | blue-600 | blue-500 | Фон основной кнопки |
| --color-surface-elevated | white | neutral-900 | Фон карточек, листов, панелей |
Визуальное регрессионное тестирование
Дополните документацию токенов визуальными регрессионными тестами с помощью Percy, Chromatic или сравнения скриншотов Playwright. Визуальный регрессионный тест рендерит библиотеку компонентов при каждом изменении токена, захватывая скриншоты и сравнивая их с базовым эталоном — любое изменение цвета инициирует ревью, выявляя случайные регрессии.
Страница цветовой палитры в Storybook
В Storybook создайте выделенную историю цветовой палитры, отображающую все токены в виде цветовых образцов:
// ColorPalette.stories.jsx
const tokens = [
{ name: "--color-surface-base", label: "Surface / Base" },
{ name: "--color-surface-elevated", label: "Surface / Elevated" },
{ name: "--color-text-primary", label: "Text / Primary" },
// ...
];
export function ColorPaletteDoc() {
return (
<div>
{tokens.map(({ name, label }) => (
<div key={name} style={{ display: "flex", alignItems: "center", gap: 16 }}>
<div style={{
width: 48,
height: 48,
backgroundColor: `var(${name})`,
borderRadius: 4,
border: "1px solid rgba(0,0,0,0.1)"
}} />
<div>
<div>{label}</div>
<code>{name}</code>
</div>
</div>
))}
</div>
);
}
Это отображает фактические живые значения токенов — в любой теме, активной в Storybook, — делая документацию самопроверяемой, а не статичной.
Ключевые выводы
- Цветовая система разделяет примитивные токены (названные по позиции в шкале:
blue-600) от семантических токенов (названных по назначению:color-action-primary-bg). Компоненты всегда ссылаются на семантические токены, никогда на примитивы. - Это разделение обеспечивает тёмный режим и тематизацию путём переназначения семантических токенов на разные примитивы на уровне CSS-кастомных свойств — без единого изменения кода компонентов.
- Генерируйте шкалы оттенков (50–950) в цветовом пространстве OKLCH, где канал яркости перцептивно равномерен: равные числовые шаги дают равномерно распределённые визуальные результаты. Используйте Генератор оттенков ColorFYI для создания шкал, совместимых с Tailwind, из любого опорного цвета.
- Категории семантических токенов охватывают четыре области: поверхности (фоновые слои), текст (иерархия текстовых ролей), действия (состояния интерактивных элементов) и обратная связь (успех, предупреждение, ошибка, информация).
- В CSS определяйте примитивы в одном файле, а семантические отображения — в другом. Используйте переопределения селектора
[data-theme="dark"]для переназначения семантических токенов в тёмном режиме. - В Tailwind v3 расширяйте объект цветов конфига ссылками на CSS-кастомные свойства. В Tailwind v4 используйте CSS-ориентированный блок
@themeдля определения как примитивных, так и семантических токенов. - Тестируйте каждую пару токенов текст/поверхность на соответствие требованиям WCAG — программно, а не только вручную. Используйте Проверку контраста ColorFYI для ручной верификации при определении токенов.
- Документируйте токены в Storybook с живыми образцами, отображающими фактические значения CSS-кастомных свойств, — это гарантирует соответствие документации реальному текущему состоянию системы.
Похожие цвета
Похожие бренды
Похожие инструменты
Генератор палитр
Генерируйте гармоничные цветовые палитры, используя комплементарные, аналоговые, триадные и расщеплённо-комплементарные схемы.
Генератор оттенков
Генерируйте шкалы оттенков в стиле Tailwind CSS (50–950) из любого базового цвета для дизайн-систем.