Design token là vốn từ vựng chung giữa nhà thiết kế và lập trình viên. Chúng tồn tại dưới nhiều hình thức từ thời Salesforce Lightning — Style Dictionary, Theo, bản thảo đặc tả W3C Design Token Community Group. Điều đã thay đổi gần đây là chúng cũng trở thành vốn từ vựng chung giữa codebase và AI coding agent.

Một agent biết color.accent = #d96a3a sẽ dùng đúng giá trị đó. Một agent nhận ảnh chụp màn hình sẽ đoán "cam ấm" và tạo ra thứ gần đúng nhưng sai. Sự khác biệt tích lũy dần qua hàng chục component.

figmascope xuất tokens.json như một phần của mỗi context bundle. Bài viết này giải thích chi tiết định dạng, cách figmascope lấy token từ Figma, fallback suy luận tần suất cho các file không có Figma Variables, và cách kết nối đầu ra với các framework phổ biến. Để xem token phù hợp với quy trình xuất đầy đủ như thế nào, hãy ghé thăm ứng dụng figmascope hoặc đọc Figma to CursorFigma to Claude Code.

Định dạng tokens.json

figmascope dùng định dạng lấy cảm hứng từ W3C Design Token Community Group. Mỗi token là một đối tượng với trường $value$type, lồng nhau dưới các khóa danh mục ngữ nghĩa:

{
  "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" }
    }
  }
}

Tại sao là W3C-ish chứ không phải đặc tả chính xác?

Đặc tả W3C Design Token Community Group vẫn đang ở dạng bản thảo. figmascope tuân theo các quy ước cốt lõi ($value, $type, nhóm lồng nhau) nhưng không triển khai tất cả các trường hợp đặc biệt như kiểu composite và chuỗi phân giải alias. Đầu ra đủ ổn định để được sử dụng bởi Style Dictionary, duyệt JSON trực tiếp, hoặc một language model — đây là consumer chính.

Các kiểu token đang dùng

$type Danh mục Định dạng $value Ví dụ
color màu sắc chuỗi hex "#d96a3a"
dimension khoảng cách, bo góc chuỗi CSS có đơn vị "16px"
fontFamily kiểu chữ chuỗi tên font "Inter"
number kiểu chữ số không đơn vị 400, 1.45

Màu sắc luôn được xuất dưới dạng chuỗi hex. Nếu nguồn Figma dùng RGBA với độ mờ không đầy đủ, kênh alpha được giữ nguyên trong hex 8 chữ số (#d96a3a80).

Cách figmascope lấy token

figmascope dùng chiến lược lấy nguồn hai tầng. Figma Variables là nguồn ưu tiên; suy luận tần suất là fallback.

Tầng 1: Figma Variables

Nếu file Figma có Variables được định nghĩa (hệ thống token gốc của Figma, có sẵn trên các gói Professional và Organization), figmascope đọc chúng thông qua endpoint /v1/files/:key/variables/local của REST API. Tên variable được dùng làm khóa token, được chuẩn hóa sang kebab-case. Các mode được thu gọn về mode mặc định trừ khi file chỉ có một mode, trong trường hợp đó giá trị của mode đó được dùng trực tiếp.

Các collection variable ánh xạ tới các khóa danh mục cấp cao nhất trong tokens.json. Collection tên "Color" tạo ra tokens.color.*; "Spacing" tạo ra tokens.spacing.*. Nếu file Figma dùng tên collection không chuẩn, figmascope cố suy luận danh mục từ kiểu đã giải quyết của variable.

Tầng 2: Suy luận tần suất

Nhiều file Figma — đặc biệt là các file cũ hoặc file từ khách hàng chưa dùng Variables — không có định nghĩa variable nào. figmascope xử lý điều này bằng cách duyệt toàn bộ cây node và xây dựng histogram tần suất của màu fill, giá trị khoảng cách, bán kính góc và thuộc tính kiểu chữ.

Các giá trị xuất hiện trên ngưỡng tần suất trở thành token ứng viên. Chúng được đặt tên theo danh mục và chỉ số tuần tự (color.0, color.1...) trừ khi có thể suy luận tên dễ đọc từ cách giá trị được dùng (ví dụ: màu chỉ dùng trên background của nhiều frame trở thành color.surface).

Manifest _meta.json ghi tokensSource"variables" hoặc "inferred", để prompt agent của bạn có thể ghi chú khi suy luận được dùng:

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

Token được suy luận có ích nhưng không phải nguồn chính thống. Hãy coi chúng là điểm khởi đầu, không phải đặc tả hệ thống thiết kế.

Tham chiếu token trong layout IR

Các file IR theo màn hình (screens/*.json) tham chiếu token theo đường dẫn dùng chuỗi $ref thay vì nhúng giá trị thô. Điều này giữ IR nhỏ gọn và đảm bảo agent luôn giải quyết giá trị từ một nguồn tin cậy duy nhất:

{
  "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" }
    }
  ]
}

Agent xử lý node này giải quyết color.accent thành #d96a3aspacing.2 thành 8px từ file tokens.json song song. Không mơ hồ, không ảo giác về giá trị.

Xem giải thích IR theo màn hình để có tài liệu đầy đủ về schema node.

Dùng tokens.json với Style Dictionary

Đầu ra của figmascope tương thích với Style Dictionary với cấu hình tối thiểu. Vì đã dùng quy ước $value / $type, bạn có thể trỏ Style Dictionary trực tiếp vào 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' }]
    }
  }
};

Chạy style-dictionary build sẽ tạo ra các CSS custom property và ES module export từ cùng một file nguồn mà agent dùng.

Dùng tokens.json với Tailwind

Một script nhỏ chuyển đổi tokens.json sang phần mở rộng theme Tailwind. Cấu trúc đủ phẳng để duyệt không đệ quy với các trường hợp thông thường:

// 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

Sau đó trong tailwind.config.ts:

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

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

Dùng tokens.json với Jetpack Compose

Với các dự án Android, cấu trúc token ánh xạ gọn gàng sang một Kotlin object. Bạn có thể tạo ra nó theo chương trình hoặc nhờ Claude Code thực hiện. Với color token:

// 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
  }
}

Xem Jetpack Compose từ Figma để có hướng dẫn đầy đủ bao gồm tích hợp MaterialTheme.

Những gì tokens.json không chứa

Biết giới hạn phạm vi giúp đặt kỳ vọng đúng đắn:

Mảng warnings trong _meta.json sẽ ghi chú bất kỳ giá trị nào không thể xuất gọn gàng. Hãy xem lại trước khi chuyển bundle cho agent.

Cách tên token được suy ra

Khi figmascope đọc Figma Variables, tên variable trong Figma trở thành khóa token. Variable tên Colors/Surface/Primary trong collection tên Color trở thành tokens.color.surface.primary trong tokens.json — dấu phân cách đường dẫn là / trong Figma, . trong khóa JSON lồng nhau.

Figma cho phép tên variable có dấu cách, chữ hoa và ký tự đặc biệt. figmascope chuẩn hóa chúng:

Bước chuyển tự quan trọng với các nhóm quốc tế. Nhóm thiết kế Ukraine dùng tên variable Cyrillic như Фон (background) nhận được khóa ASCII ổn định (fon) trong đầu ra, có thể dùng trong tên class CSS và JSON mà không cần mã hóa. Tên gốc được giữ lại như trường $description nếu có trong metadata Figma Variable.

Kiểm tra độ phủ token trước khi codegen

Trước khi chuyển bundle cho agent, đáng để xác minh rằng độ phủ token trông hợp lý. Script Node nhanh kiểm tra xem tất cả giá trị màu được tham chiếu trong screen IR có giải quyết sang các mục trong tokens.json không:

// 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.`);

Không có ref bị thiếu nghĩa là agent sẽ có thể giải quyết mọi $ref trong layout IR mà không cần bịa giá trị. Nếu có ref bị thiếu, thường nghĩa là file Figma đã thay đổi sau khi bundle được xuất — chạy lại figmascope để nhận bản xuất mới. Bạn cũng có thể xem blog figmascope để có thêm mẹo về duy trì độ phủ token qua các lần lặp thiết kế.