Термином «дизайн-система» в IT давно никого не удивишь. Компании систематизируют дизайн продуктов, придумывая свои или используя чужие инструменты для управления стилями, паттернами и компонентами.
Badoo не является исключением: с помощью нашей дизайн-системы Cosmos мы поддерживаем общие принципы дизайна для четырёх приложений, работающих на четырёх платформах.
Одна из первых и основных вещей, с которой начинается работа по созданию дизайн-системы, — это токены (или переменные), которые определяют значения разных сущностей системы.
Как это работает? Например, у вас есть приложение для двух платформ. Вместо того чтобы для каждой заново указывать в CSS-файле размер и стиль шрифтов, вы можете хранить эти значения в JSON-файле, который легко преобразуется в код для любой платформы. В дальнейшем этот файл можно использовать и в других проектах с другими кодовыми базами.
Несмотря на потенциал дизайн-токенов, во многих компаниях их структура остаётся довольно простой, что сильно ограничивает возможности их применения.
Я хочу поделиться адаптированным переводом статьи моего коллеги Кристиано Растелли (Cristiano Rastelli), который несколько лет развивает дизайн-систему Cosmos. На примере своего опыта он показывает, как работать с токенами более эффективно: добавлять метаданные и использовать их для описания свойств компонентов.
Под катом — небольшой рассказ о том, как выглядят дизайн-токены в Badoo и почему они значительно упрощают процесс разработки.
В последние месяцы я не мог не заметить, как много людей (и компаний) подразумевают под дизайн-токенами только цвет, типографику и интервалы, а в некоторых случаях — тайминг, размеры и возвышение (elevation).
Они упускают главное: дизайн-токены могут быть чем-то большим, чем всё перечисленное.
В этой статье я поделюсь своим мнением и опытом по теме и покажу, что и как можно сделать с помощью токенов.
Дизайн-токены для цветов, типографики и интервалов
Как я уже говорил, многие считают дизайн-токены «набором ключевых свойств дизайна приложения или сайта». Это простое определение часто вводит в заблуждение и ограничивает возможности применения токенов.
Например, взгляните на эту песочницу: Design System Playground. Всё, что создатели предлагают менять в ней, — это цвета и типографика.
Попробуйте этот инструмент для управления дизайн-токенами — и вы увидите, что он тоже основан на идее «ключевых» значений свойств дизайна:
Скриншот интерфейса, который используется для добавления нового дизайн-токена на сайте designtokens.dev
Посмотрите, сколько плагинов для экспорта дизайн-токенов создано для Figma, Sketch и Framer. В основе каждого из них лежит идея экспорта гранулированных и изолированных значений цветов, размера шрифтов и высоты строк, интервалов и т. д.
Изучите разделы про дизайн-токены на сайтах разных дизайн-систем: в большинстве случаев там будет написано лишь про цвета, типографику, интервалы, возвышение, тайминг, размеры и прочее.
Хочу уточнить: в этом нет ничего плохого. Но, как я сказал выше, я считаю, что дизайн-токены способны на большее.
По моему опыту, огромный потенциал и сила дизайн-токенов проявляются в полной мере тогда, когда они:
- используются для выражения свойств и значений компонентов;
- аннотируются с помощью метаинформации.
Дизайн-токены для компонентов
Нет причин не использовать дизайн-токены для описания свойств компонентов.
С технической точки зрения дизайн-токены являются упорядоченными списками пар «ключ — значение», которые описывают дизайнерские решения. То, какими правилами и ограничениями мы будем руководствоваться при описании через них интерфейса, — лишь вопрос удобства (и контекста).
В некоторых случаях (особенно когда дизайн-система находится на ранней стадии развития) целесообразно ограничиться ключевыми значениями (цветами, типографикой, интервалами). Но когда система усложняется, когда в неё добавляется всё больше и больше компонентов, то хорошо бы использовать дизайн-токены и для них.
Более того, основные цвета и шрифты меняются нечасто (например, при ребрендинге), а вот компоненты, по моему опыту, развиваются непрерывно. Например, в нашу дизайн-систему в последние месяцы было добавлено много новых компонентов. Некоторые из имеющихся были усовершенствованы, а какие-то — даже отрефакторены. Несколько компонентов были расширены с целью использования их в других контекстах или визуальных состояниях. И лишь пара цветов и один шрифт были изменены.
Как мы в Badoo используем дизайн-токены
Каждый раз при создании компонента мы обсуждаем с дизайнерами или разработчиками, которые работают над дизайн-системой, возможные состояния и варианты компонента, а также его дизайнерские решения. И затем переводим все эти спецификации в конкретные дизайн-токены.
Пример набора дизайн-токенов для компонента (Lifestyle Badge)
Вот так, к примеру, выглядит JSON-файл для компонента Lifestyle Badge (для управления токенами мы используем Style Dictionary):
{
"lifestylebadge": {
"height": {
"value": "34",
"type": "size"
},
"border_radius": {
"value": "17",
"type": "size"
},
"padding": {
"start": {
"value": "{spacing.md.value}",
"type": "size"
},
"end": {
"value": "{spacing.md.value}",
"type": "size"
}
},
"icon_size": {
"value": "{icon.size.md.value}",
"type": "size"
},
"spacing_icon_text": {
"value": "{spacing.xsm.value}",
"type": "size"
},
"base": {
"text_color": {
"value": "{color.gray_dark.value}",
"type": "color"
},
"background_color": {
"value": "{color.gray_light.value}",
"type": "color"
}
},
"selected": {
"text_color": {
"value": "{color.white.value}",
"type": "color"
},
"background_color": {
"value": "{color.primary.value}",
"type": "color"
}
}
}
}
Эти значения преобразуются в разные форматы и распределяются по различным платформам (мобильный веб, iOS, Android) и продуктам (сейчас мы поддерживаем четыре основных бренда и несколько немарочных (white label brands)).
Как только токены для компонента добавляются в систему, разработчики, которые над ним работают, получают все необходимые дизайн-спецификации:
Так выглядят токены Lifestyle Badge под Android в нашей дизайн-системе
На первый взгляд подход выглядит нерациональным и усложняющим систему. Но мы убедились в том, что это позволяет нам распределять информацию по «потребителям» — разработчикам — надёжным и отлаженным способом, что уменьшает вероятность возникновения путаницы, недопонимания и человеческих ошибок.
Со временем разработчики начинают требовать наличия конкретных токенов при добавлении компонентов в систему. В этом случае отпадает необходимость открывать Sketch-файл (или страницу в Sympli) и выяснять размеры, цвета и спецификации компонентов. Вместо этого им достаточно обновить в своих кодовых базах версию пакета дизайн-токенов и запустить скрипт синхронизации/обновления, после чего они автоматически получат все спецификации, представленные в виде легкочитаемых имён переменных.
Также мы с помощью иллюстраций показываем, как и где определены и используются дизайн-токены компонентов.
Пример набора дизайн-токенов для другого компонента (Action Sheet). С их помощью можно выразить даже выравнивание по вертикальной оси (gravity)!
Ещё один важный аспект такого подхода заключается в том, что вы получаете все те же преимущества, что и при использовании токенов для базовых свойств дизайна.
- В сложных дизайн-системах наподобие нашей это упрощает создание многопродуктовых компонентов: это те же самые компоненты с тем же кодом, которые по-разному выглядят и работают в разных продуктах/брендах. Таким образом, снижается стоимость масштабирования — например, при добавлении новых приложений в портфолио.
- Когда дизайнер решает обновить визуальный стиль компонента (например, фоновый цвет) или его макет/геометрию (например, горизонтальный отступ), то для этого требуется лишь обновить несколько значений в одном JSON-файле и изменить в кодовой базе номер версии импортируемого токена. Больше ничего: стоимость изменений чрезвычайно низкая!
Если рассматривать дизайн-токены в качестве способа передачи информации, то разумно использовать их и для компонентов.
Метаинформация дизайн-токенов
Оба основных инструмента управления дизайн-токенами (Theo и Style Dictionary) поддерживают добавление метаданных к паре «ключ — значение». Их можно использовать для добавления комментариев и аннотаций, однако по-настоящему сила метаданных раскрывается тогда, когда их используют для создания дополнительного смыслового слоя.
Например, вы можете добавить информацию о типе значений, чтобы использовать её позднее, например при обработке значений для получения конкретных результатов. Вы можете добавить информацию о группировке, чтобы потом с её помощью организовать или отфильтровать значения так, как вам нужно. Можно добавлять самые разные виды информации в зависимости от контекста и потребностей. То есть вы добавляете то, что релевантно для вас, вашего контекста, способа применения токенов и задач.
Как мы используем метаданные
Первой метаинформацией, которую мы добавили после внедрения дизайн-токенов в нашу дизайн-систему, стала «документация/комментарий» («documentation/comment»). С её помощью мы ввели в токен дополнительные данные, которые будут отображаться на сайте дизайн-системы. Мы применили пространство имён documentation, а не просто свойство comment, на тот случай, если нам понадобится добавить больше информации: к примеру, если дизайн-токен устареет и нужно будет описать его замену.
Вот пример добавления комментария в дизайн-токен:
{
"actionsheet": {
…
"item": {
"height": {
"value": "48",
"type": "size",
"documentation": {
"comment": "Notice: this is the 'internal' size, the border will be added as extra"
}
},
…
}
}
}
А так выглядит документация этого токена:
Сразу после этого мы добавили второй вид метаинформации — «тип» ("type"). Это скорее семантическое понятие, а не программистское.
Вот примеры типов, которые мы используем:
{
"color": {
"primary": {
"value": "{color.palette.purple_grape.value}",
"type": "color"
}
},
"icon": {
"size": {
"xsm": {
"value": "10",
"type": "size"
}
}
},
"tooltip": {
"shadow": {
"opacity": {
"value": "0.08",
"type": "opacity"
}
},
"animation": {
"timing_bounce": {
"value": "0.9",
"type": "time",
"unit": "s"
}
}
},
"chat": {
"bubble": {
"relative_width": {
"value": "0.8",
"type": "ratio"
}
}
},
"actionsheet": {
"gravity": {
"value": "center",
"type": "string"
}
}
}
Метаданные «тип» используются в скриптах постобработки для генерирования конкретных результатов для разных платформ. Вот фрагмент кода шаблона, который используется для генерирования XML-файла под Android:
В зависимости от «типа» токена мы генерируем значения с форматированием, необходимым для Android
А это отформатированный под наши требования результат:
Сгенерированный под Android XML-файл с соответствующим форматом/типом, который зависит от метазначения "type" в токене
Позднее мы добавили третий вид метаданных: «группа» ("group"). Это важнейший вид метаинформации, который используется по многим причинам.
Фильтрация
Для некоторых продуктов и задач нам нужно создавать подмножества токенов (например, только цвета). Для этого мы добавили в словарь стилей конкретный фильтр:
Скрипт для фильтрации «правильных» цветов
Вы могли заметить, что здесь мы используем два условия: одно для выбора токенов по типу «цвет» ("color"), второе — для отсеивания любых «псевдонимов» ("alias"). Это сделано потому, что мы используем специальные вспомогательные промежуточные цвета (вроде color-palette-purple-grave) в качестве указателей на их шестнадцатеричные значения, но поскольку они не нужны нам в виде сгенерированных токенов, мы отмечаем их флагом isAlias.
Затем этот фильтр используется для генерирования файлов только с цветами:
Мы фильтруем дизайн-токены в зависимости от метаданных "type", чтобы сгенерировать файлы только с цветами
Это лишь один пример того, как с помощью метаданных можно фильтровать токены и генерировать конкретные файлы. Можно придумать много других способов применения в зависимости от ваших потребностей и контекста.
Группировка
Ещё один способ использования метаданных — группировка дизайн-токенов по определённым правилам.
Например, с помощью атрибута «группа» ("group") можно создавать списки токенов, относящихся к одной группе, а затем использовать эти списки (или ассоциативные массивы) в кодовой базе.
Сгруппируем дизайн-токены по группам в зависимости от значений цветов и размеров иконок:
// color/xxx.json
{
"color": {
"primary": {
"value": "{color.palette.purple_grape.value}",
"type": "color",
"group": "brand"
},
…
"generic_red": {
"value": "{color.palette.pink_salmon.value}",
"type": "color",
"group": "generic"
},
…
"gray_dark": {
"value": "#767676",
"type": "color",
"group": "mono"
},
"feature": {
"verification": {
"value": "{color.palette.blue_neon.value}",
"type": "color",
"group": "features"
},
…
},
"provider": {
facebook": {
"value": "#4867aa",
"type": "color",
"group": "providers"
},
…
},
"others": {
error": {
"value": "{color.generic_red.value}",
"type": "color",
"group": "others"
},
…
}
}
}
// icon.json
{
"icon": {
"size": {
"xsm": {
"value": "10",
"type": "size",
"group": "size"
},
"sm": {
"value": "16",
"type": "size",
"group": "size"
},
"md": {
"value": "22",
"type": "size",
"group": "size"
},
"lg": {
"value": "30",
"type": "size",
"group": "size"
},
"xlg": {
"value": "36",
"type": "size",
"group": "size"
},
"xxlg": {
"value": "46",
"type": "size",
"group": "size"
}
},
"jumbo-size": {
"sm": {
"value": "{brick.size.sm.value}",
"type": "size",
"group": "size"
},
"md": {
"value": "{brick.size.md.value}",
"type": "size",
"group": "size"
},
"lg": {
"value": "{brick.size.lg.value}",
"type": "size",
"group": "size"
}
}
}
}
// spacing.json
{
"spacing": {
"xsm": {
"value": "4",
"type": "size",
"group": "size"
},
"sm": {
"value": "8",
"type": "size",
"group": "size"
},
"md": {
"value": "12",
"type": "size",
"group": "size"
},
"lg": {
"value": "16",
"type": "size",
"group": "size"
},
"xlg": {
"value": "24",
"type": "size",
"group": "size"
},
"xxlg": {
"value": "32",
"type": "size",
"group": "size"
},
"gap": {
"value": "{spacing.lg.value}",
"type": "size",
"group": "size"
}
}
}
Можно сгенерировать ассоциативные массивы в Sass (они же Sass maps, или карты):
$tokens-color-map: (
providers: (
provider-facebook: $token-color-provider-facebook,
…
),
mono: (
black: $token-color-black,
gray-dark: $token-color-gray-dark,
gray: $token-color-gray,
gray-light: $token-color-gray-light,
white: $token-color-white
),
others: (
error: $token-color-error,
…
),
brand: (
primary: $token-color-primary,
…
),
generic: (
generic-red: $token-color-generic-red,
…
),
features: (
…
feature-verification: $token-color-feature-verification
),
);
$tokens-icon-map: (
size: (
size-xsm: $token-icon-size-xsm,
size-sm: $token-icon-size-sm,
size-md: $token-icon-size-md,
size-lg: $token-icon-size-lg,
size-xlg: $token-icon-size-xlg,
size-xxlg: $token-icon-size-xxlg,
jumbo-size-sm: $token-icon-jumbo-size-sm,
jumbo-size-md: $token-icon-jumbo-size-md,
jumbo-size-lg: $token-icon-jumbo-size-lg
),
);
$tokens-spacing-map: (
size: (
xsm: $token-spacing-xsm,
sm: $token-spacing-sm,
md: $token-spacing-md,
lg: $token-spacing-lg,
xlg: $token-spacing-xlg,
xxlg: $token-spacing-xxlg,
gap: $token-spacing-gap
),
);
...
Теперь эти карты позволят нам объявлять подобные конструкции:
Использование в нашей кодовой базе Sass-карт, сгенерированных дизайн-токенами
Таким образом, мы не просто абстрагируемся от объявления списков классов/свойств в зависимости от цвета, размера и прочих параметров — для нас гораздо важнее то, что у разных продуктов могут быть разные списки цветов, интервалов, размеров иконок и т. д., и при этом один и тот же Sass-код теперь работает для любых списков цветов, интервалов, размеров и прочего.
Эти Sass-файлы являются универсальными и могут использоваться для компонентов разных продуктов и на разных платформах.
При добавлении или удалении цвета список обновляется автоматически. Не нужно менять код на уровне компонента или приложения. Всё управляется с помощью объявлений дизайн-токенов.
Это работает не только для CSS/Sass. Те же преимущества списков токенов, относящихся к одной группе, можно получить и в JavaScript:
На сайте дизайн-системы мы демонстрируем все возможные цвета компонентов, используя списки токенов, сгенерированные через ассоциированные с ними метаданные "group"
Это приводит нас ещё к одному способу применения — CSS-в-JS. В этом случае список возможных цветов (отступов, размеров) для компонентов становится просто циклом/картой списка объектов «ключ — значение», сгенерированным инструментом для работы с дизайн-токенами.
В рамках этого же подхода мы недавно использовали свойство "group" для генерирования TypeScript-определений. С помощью специального шаблона в словаре стилей мы можем генерировать такие файлы:
declare namespace Tokens {
namespace Color {
enum Providers {
PROVIDER_FACEBOOK = 'provider-facebook',
…
}
enum Mono {
BLACK = 'black',
GRAY_DARK = 'gray-dark',
GRAY = 'gray',
GRAY_LIGHT = 'gray-light',
WHITE = 'white'
}
enum Others {
ERROR = 'error',
…
}
enum Brand {
PRIMARY = 'primary',
…
}
enum Generic {
GENERIC_RED = 'generic-red',
…
}
enum Features {
…
FEATURE_VERIFICATION = 'feature-verification'
}
type Color = Providers | Mono | Others | Brand | Generic | Features;
}
namespace Icon {
enum Size {
XSM = 'xsm',
SM = 'sm',
MD = 'md',
LG = 'lg',
XLG = 'xlg',
XXLG = 'xxlg',
JUMBO_SM = 'jumbo-sm',
JUMBO_MD = 'jumbo-md',
JUMBO_LG = 'jumbo-lg'
}
}
namespace Spacing {
enum Size {
XSM = 'xsm',
SM = 'sm',
MD = 'md',
LG = 'lg',
XLG = 'xlg',
XXLG = 'xxlg',
GAP = 'gap'
}
}
}
export default Tokens;
Эти TypeScript-определения и перечисления позднее могут напрямую использоваться клиентскими кодовыми базами для обеспечения строгой типобезопасности (и автоматически заполняться в IDE).
В качестве ещё одного возможного использования метаданных для добавления в дизайн-токены дополнительного семантического значения, в частности взаимосвязей, можно упомянуть объявление текстовых стилей. Все мы знаем, что для создания минимально жизнеспособного текстового стиля нужно задать не меньше четырёх параметров: font-family, font-size, line-height и font-weight.
Как определить и явно объявить взаимосвязи между отдельными дизайн-токенами, чтобы создать «текстовый стиль H1»? Можно отразить это в наименованиях, чтобы все свойства одного стиля имели одинаковый префикс или суффикс. Тогда почему бы не использовать метаданные, ассоциированные с токенами, для их идентификации и фильтрации/группировки в ходе обработки?
Нам это реализовывать не нужно, но если бы потребовалось, то я однозначно воспользовался бы атрибутом метаданных, ассоциированным с разными значениями стилей.
Заключение
Основные выводы:
- Дизайн-токены — это не только способ хранения информации, но и способ её передачи.
- Дизайн-токены полезны для описания ключевых дизайн-значений, но их возможности в полной мере раскрываются при использовании для описания спецификаций UI-компонентов.
- Есть много способов придать дизайн-токенам дополнительный смысл с помощью добавления метаданных. Благодаря этому можно выделять и передавать больше информации, не ограничиваясь определением «ключ — значение».
Например, с помощью метаданных можно:
- добавлять комментарии к дизайн-токенам;
- использовать фильтры и генерировать селективные результаты;
- генерировать на основе токенов динамические списки для Sass, JavaScript, TypeScript;
- благодаря этим динамическим спискам использовать одни и те же компоненты (с одинаковым кодом) в разных продуктах и на разных платформах.
Я показал, как мы реализуем этот подход в нашей дизайн-системе. Но мы используем дизайн-токены для удовлетворения наших потребностей. Уверен, что у вас есть (или вы найдёте) множество других примеров того, как можно использовать дизайн-токены, помимо цветов, типографики и интервалов.
Всё ограничивается лишь воображением. Подумайте о том, что вы можете сделать, если добавите в токены метаинформацию, имеющую для вас конкретное значение в зависимости от того, как и где эти токены используются.
А потом обязательно расскажите об этом сообществу!
P. S. Больше о том, что такое дизайн-токены, можно узнать из онлайн-курса и выступления Джины Энн, публикации Дэнни Бэнкса, репозитория Стюарта Робсона и статьи Робина Рендла. Есть ещё репозиторий рабочей группы W3C, в который можно внести свой вклад. Чтобы получить более полное представление о том, как в Badoo используются дизайн-токены, почитайте эту статью: How to manage your Design Tokens with Style Dictionary.