Pull to refresh

Opium.Fill — стандартизация цветовой схемы глазами программиста

Reading time 11 min
Views 14K
Синее лицо, из глаз растут грибы

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

В общем, попробую объяснить, как дизайнеры используют цвет в UI и как всё это можно «типизировать», не вгоняя дизайнеров в жёсткие рамки. Приведу примеры реализации на React JS (для разработчика) и в Figma (для дизайнера). Привязки к React и Figma у схемы нет, просто мне в них привычнее.

В схеме нет ничего хитрого и уникального (может, только название). Все идеи висят в воздухе. Можно воспринимать её как мой best practice по работе с цветом в приложениях. Opium.Fill — это общие принципы, совмещённые с любовью давать всему подряд имена.

Систему можно использовать совместно с Material Design.

Статья написана для front-end разработчика и немного для дизайнера.

Оглавление


  1. Какие проблемы решаем
  2. Идеология Opium.Fill
  3. Базовые допущения
  4. Блок № 1. Родительские цвета
  5. Блок № 2. Подмены
  6. Блок № 3. Сдвиги
  7. Инверсия цвета
  8. Использование
  9. Когда нет смысла использовать
  10. Критика
  11. Заключение

Все картинки кликабельные

1. Какие проблемы решаем


1.1. 50 оттенков серого


С этой проблемой знакомы, наверное, все. Чаще всего её видно на оттенках серого цвета, но и с другими цветами (например с синим) такое случается. Какой куда ставить, где использовать? В этом путаются даже дизайнеры.

1.2. Дизайнер играется с цветами


Иногда дизайнер может поставить новый цвет просто потому, что старый ему надоел и разонравился (или потому что промахнулся пипеткой). В этом нет ничего плохого, не нужно винить дизайнера. Но проблема в том, что не всегда об изменениях узнаёт разработчик.

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

Похожая ситуация может встречаться на всех проектах, где работа идёт по Agile и дизайн меняется одновременно с разработкой.

1.3. Ночные темы и брендирование дизайна


Эта проблема вытекает из предыдущей. Если на проекте огромное множество неупорядоченных переменных, то сложно вводить новые цветовые схемы.

Например, вы работаете над созданием CRM. И ваша CRM даёт клиенту возможность подстроить свою цветовую схему под фирменный стиль клиента. Если у вас нет понятной цветовой схемы, то сделать это будет сложно.

А что если вам нужна тёмная тема для приложения? Тогда предложение «Василий, давайте возьмём наши 273 переменные и систематизируем их» приводит к тому, что Василий сломал форму на 10-м шаге оформления заявки, поругался с разработчиком из соседнего департамента и через неделю сошёл с ума.

2. Идеология Opium.Fill


2.1. Не мешай дизайнеру работать


Opium.Fill придуман, чтобы «расшифровывать» дизайн, а не грузить дизайнера. Дизайнеру необязательно знать о существовании Opium.Fill, чтобы делать всё по схеме.

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

2.2. Определяй цвета «на глаз»


По таблице Менделеева можно предсказывать существование ещё не открытых элементов. Для этого заранее были выделены пустые ячейки. Мы будем пользоваться этим принципом для нашей цветовой схемы. Сделаем таблицу и оставим часть ячеек незаполненными. Но по остальным параметрам вы будете понимать даже на глаз, какой цвет должен там находиться.

Таблица Менделеева


2.3. Не расстраивайся, если получается не всё


Наша задача — оптимизировать самую большую, рутинную часть работы с цветом. Если, указывая цвет при разработке приложения, 1 раз из 100 тебе будет попадаться что-то, что не укладывается в схему, это не считается проблемой.

3. Базовые допущения


3.1. Каждому цвету — по паре


Мы считаем, что у каждого цвета есть по два «воплощения». Первое — насыщенное (условно Strong). Второе — ненасыщенное (условно Weak). Если мы видим синий цвет, то помимо того, что он синий, мы должны определить его, как Strong или Weak. Он насыщенный или ненасыщенный?

3.2. Делим цвета по функциональности


Забываем про «sky-blue», «gold», «jet-black» и тому подобное в названиях цветов. Название цвета должно отражать его функциональность, а не его hex. Теперь мы работаем с такими названиями: Base, Faint, Accent, Complement, Critic, Warning, Success.

3.3. Три блока


Блок № 1 — самый важный. Блок № 3 — самый неважный. Ниже я опишу каждый из блоков. Это разделение нужно, чтобы менее значимые для рисования интерфейса цвета нас меньше отвлекали.

4. Блок № 1. Родительские цвета


4.1. Названия


Вернёмся к нашим новым названиям цветов. Base, Faint, Accent, Complement, Critic, Warning, Success. Давайте каждый по очереди разберём.

Base


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

Чёрный и белый цвета


Faint


Так мы называем оттенки серого. Какой-нибудь второстепенный текст или сероватый фон — это Faint. Чёрный цвет с прозрачностью (если он воспринимается как серый) тоже включается сюда.

Добавился серый цвет


Accent


Это главный корпоративный цвет или цвет, который выделяет наиболее важные элементы интерфейса. К примеру, если смотреть на российские банки, то: Сбербанк — зелёный, ВТБ — синий (или красный, как посмотреть), Тинькофф — жёлтый, Альфа — красный. Давайте для удобства в нашей таблице под Accent будем иметь в виду оттенки синего.

Добавился синий цвет


Complement


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

Добавился фиолетовый цвет


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

Скрин Airbnb


Critic


Цвет для выделения ошибок и прочей крайне существенной информации. Обычно что-то красное.

Добавился красный цвет



Warning


Если вы хотите разделять критичную информацию и другую, тоже важную, но не смертельно, понадобится Warning. Обычно это что-то вроде жёлто-оранжевого.

Добавился оранжевый цвет


Success


Иногда успешно выполненное действие достаточно показать цветом Accent. Но если Accent какого-то необычного цвета (красного) или ещё по каким-то причинам хочется ввести новый цвет, то вот вам Success. Скорее всего, он окажется зеленоватым.

Добавился зелёный цвет


Вышло 7 основных названий цветов. Необязательно использовать их все. И конечно, можно дополнить набор, если у вас есть серьёзная причина на это.

4.2. Цветовые семьи


Вы, наверное, уже обратили внимание, что когда я говорю про цвет, то не указываю конкретное значение, а испьзую слова «оттенки серого», «что-то красное», «зеленоватый» и т. д. Это не просто так. Давайте введём понятие «цветовая семья» (Color Family).

Как я говорил выше, мы делим цвета по парам. Не может существовать просто Accent. Обязательно есть Accent Strong и Accent Weak. Это всегда именно два цвета, которые формируют основу семьи. Как папа и мама.

Слева Strong, справа Weak


Представим на время, что наши цвета — это семейная пара. Допустим, не важно, какого пола родители, главное, чтобы один был сильным (Strong), а второй — слабым (Weak). И примем также, что сила и слабость — черты характера, как вспыльчивость и спокойствие.

Так вот. Папа — вспыльчивый. Он часто нервничает в пробках и пинает ежей в лесу. Мама — спокойная. Она работает психологом и играет в покер. Это и есть основа нашей цветовой схемы.

Цвета Base у нас уже поделены (чёрный и белый). Поделим остальные на семьи:

Все цвета поделены на 2. Один насыщенный, второй еле заметный


5. Блок № 2. Подмены


5.1. Контекст


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

Скрин Bitbucket


У любого цвета есть контекст, в котором он используется. Цвет может применяться для бэкграунда, текста, линий, иконок. Это всё мы и называем контекстом. В любом из этих случаев цвет может потребоваться как-то изменить, чтобы он лучше подходил под контекст. Такое изменение мы называем подменой (Substitution).

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

Тот же список цветов, поделённых на два, но под ним добавились дополнительные строчки. Будем называть это таблицей


Мы добавили ячейки подмены только для цветов Strong. Цвета Weak в блоке № 2 показывают, как этот цвет выглядит на тёмном бэкграунде. Пока не будем их трогать, но в конце ещё к ним вернёмся.

Если подмена не заполнена, значит, цвет для текста, иконок и всего остального
берётся из блока № 1.
Примечание. Мы можем подменять цвета только в примитивных элементах дизайна. Этот набор взят почти 1:1 из графических редакторов, которыми пользуются дизайнеры. Там есть возможность нарисовать прямоугольник (бэкграунд), добавить блок текста, нарисовать линию или добавить какую-то необычную фигуру, например звёздочку (считай иконку).

5.2. Fancy


Есть ещё один особенный вид подмены — подмена на градиент. Назовём градиенты словом Fancy — контекст по «особому поводу». Fancy един для всех. Не может быть отдельного градиента для текста, иконок и т. д. (в теории, конечно, может, но не стоит из-за такого редкого случая делать таблицу сложнее). Выделим для Fancy место внизу таблицы:

К таблице добавилась ещё одна строчка


Мы подготовились к изменениям в зависимости от контекста, дизайнеры 100% это делают. Пустые ячейки дают нам запас гибкости. Иногда подмены приходится делать чаще, иногда реже. Если блок № 2 остаётся почти пустым — это нормально (дизайнер тоже старается сократить количество цветов).

6. Блок № 3. Сдвиги


Иногда цвет нужно немного затемнить или осветлить. Но это не потому, что поменялся контекст, а просто у каждого цвета есть два дополнительных состояния: «потемнее» и «посветлее». Чаще всего дизайнеры это используют, чтобы сделать реакцию на наведение мышкой. Такое изменение цвета мы называем сдвигом (Shift). Можно сделать сдвиг вверх (темнее) или сдвиг вниз (светлее).

Вверху — тёмный цвет. Внизу — светлый


Пустая таблица теперь выглядит так:

Таблица, но теперь у каждого цвета вверху и внизу дополнительные ячейки


7. Инверсия цвета


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

Дизайн мобильного банковского приложения


Там есть текст на синей плашке, который субъективно воспринимается как Faint. А ещё есть кружки под иконками, которые я бы тоже определил в семью Faint. 

Если бы плашка была светлой, то оба этих элемента, скорее всего, были бы просто серыми. Но плашка тёмная. В нашей таблице нет подходящих цветов, их можно добавить. Как раз для этого понадобится колонка Weak в блоке № 2.

Для данного макета таблица выглядит так:

В таблице заполнено ещё часть ячеек, большая часть остаётся пустыми


Обратите внимание, что теперь линии будут нарисованы более светлым Faint Strong, это тоже отразилось в таблице. Ещё добавились градиенты для Accent и сдвиги для родительских Faint (они пригодятся, чтобы нарисовать поле поиска).
Примечание. Не нужно путать инверсию и ночную тему. У ночной темы есть много своих нюансов по цветам. Так что лучше создать для неё отдельную таблицу.

8. Использование


8.1. CSS, React


Попробуем нарисовать такую кнопку

Кнопка


В чистом CSS переменные можно организовать так:

:root {
  /* Родительские цвета */

  --base-strong: #000;
  --base-weak: #fff;

  --faint-strong: #8994A6;
  --faint-weak: #F6F8FB;

  --accent-strong: #0070FF;
  --accent-weak: #EBF4FF;

  --complement-strong: #8889E2;
  --complement-weak: #EEECFD;

  --critic-strong: #F74545;
  --critic-weak: #FDEDED;

  --warning-strong: #F8AE4F;
  --warning-weak: #FCEBCF;

  --success-strong: #27AE60;
  --success-weak: #DEF8E9;

  /* Cдвиги родительских цветов */
  --faint-strong-down: #A5ADBB;
  --faint-weak-up: #ECEEF5;
}

/* Подмены */
/* Контекст добавляем в виде классов */

.back {
  --faint-weak: rgba(255, 255, 255, 0.15);
}

.text {
  --faint-weak: rgba(237, 241, 247, 0.5);
}

.line {
  --faint-strong: #EDEFF2;
}

.icon {}

.fancy {
  --accent-strong: linear-gradient(132deg, #3F89EE, #5447FF);

  /* Сдвиги */
  --accent-strong-down: linear-gradient(132deg, #448FF3, #594CFF);
}


/* Рисуем кнопку */

.button {
  background: var(--accent-strong);
  color: var(--base-weak);
  /* Дальше просто оформительство */
}

.button:hover {
  background: var(--accent-strong-down);
}

И тогда, чтобы создать кнопку в HTML, нужно будет добавить такую разметку:

<button class="fancy button">
  <div class="text">
    Hello Button
  </div>
</button>

В CSS нет средств, чтобы нативно отследить контекст, и тогда придётся его указывать вручную через классы. Можно, конечно, пытаться понять контекст по используемым тегам, но это решение на любителя.

Ещё способ, подсказанный Snelsi в комментариях к статье, — определять контекст через data-атрибуты вместо классов:

<button data-context="fancy" class="button">
  <div data-context="text">
    Hello Button
  </div>
</button>

Другой вариант использования data-атрибутов — сразу задавать через них конечный цвет:

<button
  data-back-fancy
  data-back-color="accent-strong"
  data-text-color="base-weak"
  class="button"
>
  Hello Button
</button>

Тогда CSS должен содержать следующее:

[data-back-color="accent-strong"] {
  background-color: #0070FF;
}

[data-back-fancy][data-back-color="accent-strong"] {
  background-image: linear-gradient(132deg, #3F89EE, #5447FF);
}

[data-text-color="base-weak"] {
  color: #fff;
}

/* И так далее для остальных цветов */


На React код может выглядеть так:

class Button extends React.Component {
  render() {
    return (
      <Box fill="accentStrong" fancy className="button">
        <Font fill="baseWeak">Hello Button</Font>
      </Box>
    )
  }
}

// Или немного покороче
class Button extends React.Component {
  render() {
    return (
      <Box.Accent fancy className="button">
        <Font>Hello Button</Font>
      </Box.Accent>
    )
  }
}

// Box и Font — базовые компоненты для примитивов, по ним мы понимаем контекст

// Во втором примере даже можно не указывать Strong или Weak (а для текста вообще цвет не указывать)
// Таблица часто позволяет компонентам подбирать цвета автоматически
// Хавер тоже можно иногда высчитать автоматически

// Как реализовать базовые компоненты, описывать не буду
// Думаю, вы сами тут разберётесь
// Да и к тому же, это не обязательно для Opium.Fill
// Здесь показан только один из способов реализации

// В классе 'button' уже никак не указываем цвет
// С цветом мы разобрались на примитивах

Чтобы не быть привязанным к CSS (вы же не только для браузера делаете приложения), переменные цвета можно хранить в json:

{
  "color": {
    "parents": {
      "baseStrong": "#000",
      "baseWeak": "#fff",

      "faintStrong": {
        "default": "#8994A6",
        "shiftDown": "#A5ADBB"
      },
      "faintWeak": {
        "default": "#F6F8FB",
        "shiftUp": "#EDEFF2"
      },

      "accentStrong": "#0070FF",
      "accentWeak": "#EBF4FF",

      "complementStrong": "#8889E2",
      "complementWeak": "#EEECFD",

      "criticStrong": "#F74545",
      "criticWeak": "#FDEDED",

      "warningStrong": "#F8AE4F",
      "warningWeak": "#FCEBCF",

      "successStrong": "#27AE60",
      "successWeak": "#DEF8E9"
    },

    "context": {
      "back": {
        "faintWeak": "rgba(255, 255, 255, 0.15)"
      },

      "text": {
        "faintWeak": "rgba(237, 241, 247, 0.5)"
      },

      "line": {
        "faintStrong": "#EDEFF2"
      },

      "icon": {},

      "fancy": {
        "accentStrong": {
          "default": "linear-gradient(132deg, #3F89EE, #5447FF)",
          "shiftDown": "linear-gradient(132deg, #448FF3, #594CFF)"
        }
      }
    }
  }
}

8.2. Цветовая схема в Figma


Мне кажется, тут будет верным разделить цвета по контекстам так, чтобы названия контекста были видны. А сдвиги добавлять в самый конец этих блоков. Если сдвиг действительно используется только для хавера, то даже не стоит его добавлять в палитру, чтобы лишний раз не отвлекал.

Список цветов из Figma


9. Когда нет смысла использовать систему


9.1. Необычный дизайнерский проект


Opium.Fill устроен так, что базовые цвета — это чёрный и белый (или те, что субъективно похожи на них до степени смешения). Если нужно много писать синим по красному или постоянно использовать в одном интерфейсе самые разные цвета, то Opium.Fill — не лучшее решение.

9.2. Маленький проект


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

9.3. Вы уже пользуетесь чем-то другим


Если ваша команда использует Material Design (и не только) и все всем довольны, то нет смысла менять или корректировать то, что работает.

10. Критика


Приведу несколько часто озвучиваемых проблем с Opium.Fill. На часть из претензий попробую ответить. Можно считать это открытой темой. Если кто-то столкнётся со сложностями, буду рад, если поделитесь со мной.

10.1. Переменных всё равно много


Любознательный читатель уже подсчитал, что у нас есть ячейки для 252 переменных. Чем это лучше того, что было в начале?

Могу уверить, все эти цвета никогда не будут использоваться одновременно. Для них есть место, но это пустые ячейки, как неоткрытые элементы в таблице Менделеева. На реальных проектах у нас получается «открыть» до 30 элементов.

Если в вашей таблице накапливается больше 50 штук, то, наверное, что-то идёт не так и схема, к сожалению, не помогает это исправить.

10.2. Для закраски графиков придётся сильно расширять таблицу


Если у вас много графиков или нужно покрасить категории товаров в разные цвета (а их, например, 20 штук), то придётся добавлять новые цветовые семьи. Семи штук, что я описал выше, не хватит.

10.3. Зачем добавлять подмены, если в теории можно все потребности покрыть сдвигами?


Я исхожу из идеи, что подмены в зависимости от контекста — это более «высокоуровневое» объяснение того, как дизайнер использует цвета. И поэтому подмены проще понять и использовать.

Сдвиги пригодятся для редких цветов, их лучше заполнять в случаях:

  1. Обработки событий (таких как хавер или нажатие)
  2. Когда нужен дополнительный спектр оттенков серого

Заключение


Концепция появилась в начале 2018 года и с тех пор почти не изменилась. В моей любимой организации мы внедрили Opium.Fill одновременно и в дизайн и в разработку. Иногда приходилось реализовывать дизайн, сделанный другими командами (из других организаций), но это не мешало использованию цветовой схемы, мы сталкивались со сложностями, но их получалось решить. Часть проектов уже вышли в продакшен.

Если тебе близка тема управления цветами на проекте, возможно, будет интересно почитать и про другие варианты: Material Design, Atlassian Design

Спасибо


Придумали схему и написали статью — Денис Элиановский, команда JTC
Иллюстрация в шапке — Елена Ефимова

Версия на английском

Библиотека на React, использующая Opium.Fill — github.com/opium-pro/themeor
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+16
Comments 10
Comments Comments 10

Articles