Дизайн-токены — это общий словарь между дизайнерами и разработчиками. Они существуют в разных формах ещё со времён Salesforce Lightning — Style Dictionary, Theo, черновик спецификации W3C Design Token Community Group. Что изменилось в последнее время — они стали также общим словарём между вашей кодовой базой и AI-агентами для написания кода.

Агент, знающий, что color.accent = #d96a3a, будет использовать именно это значение. Агент, получивший скриншот, угадает «тёплый оранжевый» и выдаст что-то близкое, но неверное. Эта ошибка накапливается на десятках компонентов.

figmascope экспортирует tokens.json как часть каждого бандла контекста. В этой статье подробно объясняется формат, как figmascope извлекает токены из Figma, резервный механизм вывода по частоте для файлов без Figma Variables, и как подключить результат к распространённым фреймворкам. Чтобы увидеть, как токены вписываются в полный рабочий процесс экспорта, посетите приложение figmascope или прочитайте Figma и Cursor и Figma и Claude Code.

Формат tokens.json

figmascope использует формат, вдохновлённый спецификацией W3C Design Token Community Group. Каждый токен — объект с полями $value и $type, вложенный под ключи семантических категорий:

{
  "color": {
    "surface":      { "$value": "#f6f2ea", "$type": "color" },
    "surface-2":    { "$value": "#efe9dc", "$type": "color" },
    "ink":          { "$value": "#1f1d1a", "$type": "color" },
    "ink-muted":    { "$value": "#4a4641", "$type": "color" },
    "accent":       { "$value": "#d96a3a", "$type": "color" },
    "accent-soft":  { "$value": "#f2c7a8", "$type": "color" },
    "good":         { "$value": "#6a8f5a", "$type": "color" },
    "warn":         { "$value": "#c89a3a", "$type": "color" },
    "bad":          { "$value": "#b8553a", "$type": "color" }
  },
  "spacing": {
    "1":  { "$value": "4px",   "$type": "dimension" },
    "2":  { "$value": "8px",   "$type": "dimension" },
    "3":  { "$value": "12px",  "$type": "dimension" },
    "4":  { "$value": "16px",  "$type": "dimension" },
    "6":  { "$value": "24px",  "$type": "dimension" },
    "8":  { "$value": "32px",  "$type": "dimension" },
    "12": { "$value": "48px",  "$type": "dimension" },
    "16": { "$value": "64px",  "$type": "dimension" }
  },
  "radius": {
    "sm":   { "$value": "4px",  "$type": "dimension" },
    "md":   { "$value": "8px",  "$type": "dimension" },
    "lg":   { "$value": "16px", "$type": "dimension" },
    "full": { "$value": "9999px", "$type": "dimension" }
  },
  "typography": {
    "body": {
      "fontFamily": { "$value": "Inter",  "$type": "fontFamily" },
      "fontSize":   { "$value": "14px",   "$type": "dimension" },
      "fontWeight": { "$value": 400,       "$type": "number" },
      "lineHeight": { "$value": 1.45,     "$type": "number" }
    },
    "heading": {
      "sm": {
        "fontFamily": { "$value": "Inter",  "$type": "fontFamily" },
        "fontSize":   { "$value": "18px",   "$type": "dimension" },
        "fontWeight": { "$value": 600,       "$type": "number" },
        "lineHeight": { "$value": 1.25,     "$type": "number" }
      }
    },
    "mono": {
      "fontFamily": { "$value": "JetBrains Mono", "$type": "fontFamily" },
      "fontSize":   { "$value": "13px",   "$type": "dimension" },
      "fontWeight": { "$value": 400,       "$type": "number" }
    }
  }
}

Почему «W3C-образный», а не строго по спецификации?

Спецификация W3C Design Token Community Group всё ещё находится в статусе черновика. figmascope следует основным соглашениям ($value, $type, вложенные группы), но не реализует все крайние случаи, такие как составные типы и цепочки разрешения псевдонимов. Вывод достаточно стабилен для потребления с помощью Style Dictionary, прямого обхода JSON или языковой моделью — которая является основным потребителем.

Используемые типы токенов

$type Категория Формат $value Пример
color цвет hex-строка "#d96a3a"
dimension отступы, радиус CSS-строка с единицей измерения "16px"
fontFamily типографика строка с названием шрифта "Inter"
number типографика число без единиц измерения 400, 1.45

Цвета всегда выводятся как hex-строки. Если источник в Figma использует RGBA с неполной прозрачностью, альфа-канал сохраняется в 8-значном hex (#d96a3a80).

Как figmascope извлекает токены

figmascope использует двухуровневую стратегию извлечения. Figma Variables — предпочтительный источник; вывод по частоте — резервный вариант.

Уровень 1: Figma Variables

Если в Figma-файле определены Variables (нативная система токенов Figma, доступная на тарифах Professional и Organization), figmascope читает их через эндпоинт /v1/files/:key/variables/local REST API. Имена переменных используются как ключи токенов, приведённые к kebab-case. Режимы сворачиваются к режиму по умолчанию, если только файл не имеет единственного режима — тогда значения этого режима используются напрямую.

Коллекции переменных отображаются в ключи категорий верхнего уровня в tokens.json. Коллекция с именем «Color» порождает tokens.color.*; «Spacing» порождает tokens.spacing.*. Если ваш Figma-файл использует нестандартные имена коллекций, figmascope пытается вывести категорию из разрешённого типа переменной.

Уровень 2: Вывод по частоте

Многие Figma-файлы — особенно старые или файлы от клиентов, не перешедших на Variables — не имеют определений переменных. figmascope справляется с этим, обходя всё дерево узлов и строя гистограммы частот для цветов заливки, значений отступов, радиусов скругления и свойств типографики.

Значения, встречающиеся выше порогового значения частоты, становятся кандидатами в токены. Они именуются по категории и порядковому индексу (color.0, color.1...), если только из контекста использования нельзя вывести читаемое имя (например, цвет, используемый только на фонах нескольких фреймов, получит имя color.surface).

Манифест _meta.json записывает значение tokensSource как "variables" или "inferred", чтобы промпт агента мог учитывать случай вывода:

// _meta.json — inferred fallback
{
  "tokensSource": "inferred",
  "warnings": [
    {
      "code": "tokens-inferred",
      "message": "No Figma Variables found. Tokens were inferred from usage frequency."
    }
  ]
}

Выведенные токены полезны, но не авторитетны. Рассматривайте их как отправную точку, а не как спецификацию дизайн-системы.

Ссылки на токены в IR разметки

Файлы IR для каждого экрана (screens/*.json) ссылаются на токены по пути через строки $ref, а не встраивают сырые значения. Это делает IR компактным и гарантирует, что агент всегда разрешает значения из единственного источника истины:

{
  "kind": "stack",
  "name": "PrimaryButton",
  "direction": "horizontal",
  "gap": { "$ref": "spacing.2" },
  "padding": {
    "top": 10, "right": 16, "bottom": 10, "left": 16
  },
  "background": { "$ref": "color.accent" },
  "radius": { "$ref": "radius.sm" },
  "children": [
    {
      "kind": "leaf",
      "name": "ButtonLabel",
      "type": "text",
      "style": { "$ref": "typography.body" },
      "color": { "$ref": "color.surface" }
    }
  ]
}

Агент, обрабатывающий этот узел, разрешает color.accent в #d96a3a и spacing.2 в 8px из параллельного файла tokens.json. Никакой двусмысленности, никаких галлюцинаций значений.

Полная документация по схеме узлов описана в статье per-screen IR explained.

Использование tokens.json со Style Dictionary

Вывод figmascope совместим с Style Dictionary с минимальной конфигурацией. Поскольку он уже использует соглашение $value / $type, можно направить Style Dictionary прямо на tokens.json:

// style-dictionary.config.js
module.exports = {
  source: ['design/tokens.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      prefix: 'fs',
      buildPath: 'src/styles/',
      files: [{ destination: 'tokens.css', format: 'css/variables' }]
    },
    js: {
      transformGroup: 'js',
      buildPath: 'src/',
      files: [{ destination: 'tokens.js', format: 'javascript/es6' }]
    }
  }
};

Запуск style-dictionary build произведёт CSS custom properties и ES-модули из того же исходного файла, который использует агент.

Использование tokens.json с Tailwind

Небольшой скрипт преобразует tokens.json в расширение темы Tailwind. Структура достаточно плоская, чтобы обходить её без рекурсии для общих случаев:

// scripts/tokens-to-tailwind.js
const fs = require('fs');
const tokens = JSON.parse(fs.readFileSync('design/tokens.json', 'utf8'));

function flattenGroup(group) {
  return Object.fromEntries(
    Object.entries(group).map(([k, v]) => [k, v.$value])
  );
}

const extend = {
  colors:      flattenGroup(tokens.color),
  spacing:     flattenGroup(tokens.spacing),
  borderRadius: flattenGroup(tokens.radius),
};

console.log(JSON.stringify(extend, null, 2));
node scripts/tokens-to-tailwind.js > design/tailwind-extend.json

Затем в tailwind.config.ts:

import extend from './design/tailwind-extend.json';

export default {
  content: ['./src/**/*.{ts,tsx}'],
  theme: { extend },
};

Использование tokens.json с Jetpack Compose

Для Android-проектов структура токенов чисто отображается в Kotlin-объект. Его можно генерировать программно или попросить это сделать Claude Code. Для токенов цветов:

// Generated from design/tokens.json
object DesignTokens {
  object Color {
    val surface    = Color(0xFFF6F2EA)
    val ink        = Color(0xFF1F1D1A)
    val accent     = Color(0xFFD96A3A)
    val accentSoft = Color(0xFFF2C7A8)
  }
  object Spacing {
    val s1 = 4.dp
    val s2 = 8.dp
    val s4 = 16.dp
    val s8 = 32.dp
  }
}

Полное руководство с интеграцией MaterialTheme смотрите в статье Jetpack Compose from Figma.

Что не входит в tokens.json

Знание границ области применения помогает сформировать корректные ожидания:

Массив warnings в _meta.json укажет на значения, которые не удалось экспортировать чисто. Проверяйте его перед передачей бандла агенту.

Как формируются имена токенов

Когда figmascope читает Figma Variables, имя переменной в Figma становится ключом токена. Переменная с именем Colors/Surface/Primary в коллекции с именем Color становится tokens.color.surface.primary в tokens.json — разделитель пути в Figma /, в JSON — вложенные ключи, разделённые ..

Figma допускает имена переменных с пробелами, заглавными буквами и специальными символами. figmascope нормализует их:

Шаг транслитерации важен для интернациональных команд. Украинская дизайн-команда, использующая кириллические имена переменных вроде Фон (фон), получает стабильный ASCII-ключ (fon) в выводе, пригодный для CSS-классов и JSON без проблем с кодировкой. Исходное имя сохраняется в поле $description, если оно присутствует в метаданных Figma Variable.

Проверка покрытия токенами перед генерацией кода

Перед передачей бандла агенту стоит убедиться, что покрытие токенами выглядит разумным. Быстрый Node-скрипт, проверяющий, разрешаются ли все цветовые значения, упомянутые в IR экранов, в записи tokens.json:

// scripts/check-token-coverage.js
const fs = require('fs');
const glob = require('glob');

const tokens = JSON.parse(fs.readFileSync('design/tokens.json', 'utf8'));
const screenFiles = glob.sync('design/screens/*.json');

function getRef(obj) {
  const refs = [];
  if (obj && typeof obj === 'object') {
    if (obj.$ref) refs.push(obj.$ref);
    for (const v of Object.values(obj)) refs.push(...getRef(v));
  }
  return refs;
}

function resolveRef(ref, tokens) {
  return ref.split('.').reduce((o, k) => o?.[k], tokens);
}

let missing = 0;
for (const file of screenFiles) {
  const screen = JSON.parse(fs.readFileSync(file, 'utf8'));
  for (const ref of getRef(screen)) {
    if (!resolveRef(ref, tokens)) {
      console.error(`Missing token: ${ref} (in ${file})`);
      missing++;
    }
  }
}
if (missing === 0) console.log('All token refs resolve.');
else console.error(`${missing} unresolved token refs.`);

Ноль пропущенных ссылок означает, что агент сможет разрешить каждый $ref в IR разметки без выдумывания значений. Если пропущенные ссылки есть, это обычно означает, что Figma-файл изменился после экспорта бандла — повторно запустите figmascope для свежего экспорта. Дополнительные советы по поддержанию покрытия токенами на протяжении итераций дизайна читайте в блоге figmascope.