Дизайн-токени — це спільна мова між дизайнерами та розробниками. Вони існують у різних формах ще з епохи 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."
    }
  ]
}

Виведені токени корисні, але не авторитетні. Ставтесь до них як до відправної точки, а не специфікації дизайн-системи.

Посилання на токени в layout 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 module exports з того ж вихідного файлу, що використовує агент.

Використання 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 object. Можна згенерувати його програмно або попросити 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 нормалізує їх:

Крок транслітерації важливий для міжнародних команд. Українська дизайнерська команда, що використовує кириличні назви змінних на кшталт Фон (background), отримає стабільний ASCII-ключ (fon) у результаті, придатний для CSS class-назв і 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 у layout IR без вигадування значень. Якщо є відсутні посилання — зазвичай це означає, що Figma-файл змінився після того, як бандл був експортований — повторно запустіть figmascope для свіжого експорту. На блозі figmascope є більше порад щодо підтримання покриття токенів між ітераціями дизайну.