javascript

Тема Tailwind CSS из дизайн‑токенов одной командой

  • пятница, 30 октября 2026 г. в 00:00:13
https://habr.com/ru/articles/1101166/

Я сам рисую интерфейсы и сам их верстаю. У меня есть библиотека стилей pbstyles: цвета, размеры и шрифты в ней описаны дизайн‑токенами. Тему для Tailwind я хотел собирать из этих токенов, а не переписывать руками после каждой правки в Figma. Так появился пакет tailwind‑dictionary.

Он берёт токены в JSON и собирает из них тему Tailwind CSS. Светлая и тёмная тема собираются из тех же файлов.

Пакет пригодится, если у вас уже есть токены, но переходить на Tailwind не хочется из‑за того, что тему придётся вести отдельно. Или если вы уже на Tailwind и хотите выгружать переменные из Figma и сразу получать тему.


Почему не попросить ИИ

ИИ может забрать переменные из Figma и собрать тему. Но токены ИИ стоят денег, а тема пересобирается после каждой правки в макете. Тратить их на одну и ту же рутину жалко, лучше оставить на задачи посложнее. К тому же ИИ может каждый раз собрать тему чуть по‑разному.

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

Что умеет

Пакет читает токены в новом формате W3C Design Tokens (DTCG 2025.10) и в старом формате Style Dictionary. Подходит выгрузка из Figma, Tokens Studio и Terrazzo.

Собирает тему для Tailwind 4 и для Tailwind 3. Тем может быть сколько угодно: светлая, тёмная, контрастная, тема второго бренда. В тему попадают цвета, размеры, шрифты, текстовые стили, тени, брейкпоинты и анимации.

Вся настройка — один файл config.json. Редактор подсказывает его поля и подсвечивает ошибки. Есть режим --watch, который пересобирает тему при изменении токенов.

Что отдаёт Figma

Figma выгружает переменные сама: в окне Variables правый клик по коллекции → Export modes. На каждый режим получается файл *.tokens.json.

Возьмём простой пример. Палитра и радиусы лежат в коллекции без режимов. Цвета primary, background и foreground лежат в коллекции с режимами Light и Dark и ссылаются на палитру.

Так выглядит палитра:

{
  "color": {
    "blue": {
      "600": {
        "$type": "color",
        "$value": {
          "colorSpace": "srgb",
          "components": [0.149, 0.388, 0.922],
          "alpha": 1,
          "hex": "#2563EB"
        },
        "$extensions": { "com.figma.variableId": "VariableID:1:3" }
      }
    }
  },
  "radius": {
    "4": { "$type": "number", "$value": 4 }
  }
}

А так режим Light:

{
  "color": {
    "primary": {
      "$type": "color",
      "$value": "{color.blue.600}"
    }
  }
}

В CSS это напрямую не вставишь. Цвет записан объектом, primary ссылается на другую переменную, а у радиуса нет единицы: --radius-4: 4 браузер не поймёт. Пакет всё это разбирает сам.

Собираем тему

npm install --save-dev tailwind-dictionary

Палитру кладём в tokens/Primitives.tokens.json, режимы — в tokens/Light.tokens.json и tokens/Dark.tokens.json. Рядом создаём config.json:

{
  "version": 4,
  "source": ["tokens/Primitives.tokens.json"],
  "themes": {
    "light": ["tokens/Light.tokens.json"],
    "dark": ["tokens/Dark.tokens.json"]
  },
  "output": "./styles",
  "themeAliases": {
    "color": "color",
    "radius": "radius",
    "spacing": "1px"
  }
}

В source лежат токены, общие для всех тем, в themes — файлы тем. В themeAliases слева ключ темы Tailwind, справа группа токенов. Если в Figma радиусы называются rounded, пишем "radius": "rounded". Строка "spacing": "1px" задаёт шаг отступов: класс p-16 даст ровно 16 пикселей, как в макете.

Запускаем сборку и подключаем результат после Tailwind:

npx tailwind-dictionary
@import 'tailwindcss';
@import './styles/tailwind/theme.css';

Теперь в проекте работают классы bg-primary, text-foreground и rounded-4.

Что получилось

Файл theme.css, светлая тема для краткости убрана:

:root {
  --theme-color-primary: #2563eb;
  --theme-color-background: #f9fafb;
  --theme-color-foreground: #111827;
}

[data-theme='dark'] {
  --theme-color-primary: #3380f5;
  --theme-color-background: #111827;
  --theme-color-foreground: #f9fafb;
}

@theme {
  --*: initial;

  --color-*: initial;
  --color-blue-400: #3380f5;
  --color-blue-600: #2563eb;
  --color-gray-50: #f9fafb;
  --color-gray-900: #111827;

  --radius-0: 0;
  --radius-4: 4px;
  --radius-8: 8px;

  --spacing: 1px;
}

@theme inline {
  --color-primary: var(--theme-color-primary);
  --color-background: var(--theme-color-background);
  --color-foreground: var(--theme-color-foreground);
}

Цвета стали обычными hex, ссылки заменились значениями. Радиус 4 превратился в 4px. Единицу можно поменять опцией numberUnit: "rem" делит число на 16, false оставляет как есть.

Строка --*: initial убирает стандартную тему Tailwind. В проекте остаются только ваши значения, и случайный bg-blue-500, которого нет в макете, просто не соберётся.

Как переключаются темы

Цвета тем лежат в --theme-color-*, а Tailwind ссылается на них через @theme inline. Благодаря inline класс bg-primary компилируется в background-color: var(--theme-color-primary), и значение берётся на том элементе, где стоит класс. Поэтому тему можно поменять в любом месте страницы:

<html data-theme="dark">
  <body>
    <section data-theme="light">Светлый блок на тёмной странице</section>
  </body>
</html>

Без атрибута светлая и тёмная тема следуют настройкам системы. Атрибут на <html> их перекрывает, это нужно для переключателя темы на сайте.

На что я натыкался

Описания переменных Figma не выгружает. Текстовых стилей и теней в выгрузке тоже нет: в Figma это стили, а не переменные. Я описываю их токенами вручную, и токен типографики h64 превращается в класс text-h64 сразу с размером, интервалом и начертанием.

Файлы тем нельзя класть в source. Если написать tokens/**/*.json, а темы лежат в tokens/themes/, одни и те же токены придут дважды.

Старый формат Style Dictionary пакет тоже читает, но смешивать его с новым в одной сборке нельзя.

Если что‑то не так, ошибка выводится одной строкой — с именем токена и файла:

✘ Token "color.text" (tokens/color.json) references {color.nope}, which is not defined

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

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

С Figma пакет сам не синхронизируется: выгрузку нужно сделать руками и положить в репозиторий. Компоненты он тоже не собирает, только тему. Если Tailwind в проекте нет, из тех же токенов можно собрать CSS‑переменные и миксины для LESS и SCSS соседним пакетом mixin‑dictionary.

Ссылки