Mỗi lần xuất của figmascope đều bao gồm tokens.json. Đây là cầu nối giữa các giá trị trực quan của Figma và các hằng số có kiểu dữ liệu mà code của bạn cần. Bài viết này trình bày schema, cách đặt tên key, điều gì xảy ra khi file Figma không có Variables, và những hạn chế thực sự của hệ thống token.
Schema
Cấu trúc cấp cao nhất có bốn phần:
{
"spacing": {
"spacing.4": { "$value": 4, "$type": "dimension" },
"spacing.8": { "$value": 8, "$type": "dimension" },
"spacing.12": { "$value": 12, "$type": "dimension" },
"spacing.16": { "$value": 16, "$type": "dimension" },
"spacing.24": { "$value": 24, "$type": "dimension" }
},
"radius": {
"radius.4": { "$value": 4, "$type": "dimension" },
"radius.8": { "$value": 8, "$type": "dimension" },
"radius.12": { "$value": 12, "$type": "dimension" }
},
"color": {
"color.7f5cfe": { "$value": "#7f5cfe", "$type": "color" },
"color.ffffff": { "$value": "#ffffff", "$type": "color" },
"color.1a1a2e": { "$value": "#1a1a2e", "$type": "color" }
},
"typography": {}
}
Định dạng gần với W3C Design Tokens Community Group: mỗi token là một object có $value và $type. Đây không phải triển khai W3C DTCG chính thức — figmascope ra đời trước khi spec cuối được công bố và không hỗ trợ các kiểu composite như fontFamily — nhưng đủ gần để công cụ hỗ trợ DTCG có thể phân tích với điều chỉnh nhỏ.
Object typography rỗng không phải lỗi. Phần dưới sẽ giải thích.
Đặt tên key dẫn xuất từ giá trị
Khi file Figma có Variables, tên key token lấy từ tên Variable do designer đặt. spacing.md, color.brand.primary, tùy design system sử dụng.
Khi file Figma không có Variables — trường hợp phổ biến trong thực tế — figmascope rơi vào cơ chế đặt tên dẫn xuất từ giá trị. Giá trị spacing 16 trở thành spacing.16. Màu #7f5cfe trở thành color.7f5cfe. Corner radius 4 trở thành radius.4.
Đây là sự đánh đổi có chủ ý. Tên dẫn xuất từ giá trị trông xấu nhưng ổn định. Chúng được suy ra từ giá trị thực tế, nên hai lần xuất cùng một file Figma tạo ra cùng key. spacing.16 luôn có nghĩa là 16dp. Agent có thể tin tưởng vào điều này.
Phương án thay thế sẽ là tên theo vị trí như spacing.1, spacing.2... Cách đó dễ vỡ — thêm một giá trị spacing nhỏ hơn và mọi thứ dịch chuyển. Tên dẫn xuất từ giá trị không bị dịch chuyển.
Tên dẫn xuất từ giá trị là cơ chế fallback cho các file lẽ ra phải có Variables nhưng không có. Nếu design system của bạn có 40 giá trị spacing cần tên ngữ nghĩa, hãy đề nghị designer thiết lập Variables. Cơ chế suy luận fallback tồn tại cho các file thực tế, không phải thay thế cho hệ thống token thực sự. Bạn có thể chạy figmascope trên file của mình để xem nó đi theo hướng nào.
Trường tokensSource và ý nghĩa của nó
_meta.json bao gồm trường tokensSource cho biết token được suy ra như thế nào:
| Giá trị | Ý nghĩa |
|---|---|
"figma-variables" |
File Figma có Variables và chúng được sử dụng trực tiếp. Tên token do designer đặt. Phủ sóng đầy đủ. |
"inferred-from-frequency" |
Không có Variables. figmascope quét tất cả node, tìm các giá trị xuất hiện thường xuyên, đưa chúng lên thành token. Phạm vi phủ sóng phụ thuộc vào tính nhất quán của thiết kế. |
"none" |
Không có Variables và suy luận không cho kết quả hữu ích. tokens.json sẽ có các phần rỗng hoặc gần rỗng. |
Cảnh báo "tokens-inferred-from-frequency" trong _meta.json phản ánh điều này. Nếu bạn thấy nó, phạm vi phủ sóng token là cố gắng tốt nhất.
Khi tokensSource là "inferred-from-frequency", thuật toán suy luận là: tìm tất cả giá trị dimension xuất hiện trong ba node trở lên ở trường padding, gap, hoặc cornerRadius. Đưa chúng lên thành token spacing hoặc radius tương ứng. Làm tương tự với màu fill. Các giá trị chỉ xuất hiện một hoặc hai lần được coi là đặc biệt, không được đưa lên.
Cách này hoạt động tốt với thiết kế nhất quán nội bộ. Hoạt động kém với thiết kế thử nghiệm khi spacing biến đổi tự do. Các cảnh báo trong _meta.json tồn tại chính xác để agent biết mình đang ở tình huống nào.
Tại sao typography thường rỗng
Token typography yêu cầu Figma Variables với kiểu FLOAT hoặc STRING để trích xuất đáng tin cậy. Text styles tồn tại trong Figma dưới dạng shared styles, không phải Variables, và surface API cho styles khác với Variables API.
figmascope v0.4 trích xuất typography khi Variables bao gồm nó. Nó không cố gắng suy luận dựa trên tần suất cho typography vì các token typography hữu ích — font family, line height, letter spacing, tổ hợp weight — không có tên dẫn xuất từ giá trị rõ ràng như spacing.16. Key fontSize.14 ít hữu ích hơn nhiều so với typography.body.small, và tạo ra tên sai còn tệ hơn không tạo ra tên nào.
Vì vậy kết quả là trung thực: nếu file Figma của bạn có typography Variables, bạn nhận được typography tokens. Nếu không, bạn nhận được object rỗng và agent được thông báo qua CONTEXT.md rằng phạm vi phủ sóng typography có thể không đầy đủ.
// _meta.json
{
"tokensSource": "inferred-from-frequency",
"warnings": [
"tokens-inferred-from-frequency"
]
}
Agent thấy điều này và biết cần thận trọng với các tham chiếu token typography. Nó tạo ra code dự phòng với giá trị tường minh và comment TODO thay vì tự đặt ra tên token.
Agent sử dụng tokens.json như thế nào
Ràng buộc trong CONTEXT.md là: "Không bao giờ hardcode giá trị dp nếu tồn tại token trong khoảng ±2dp." Dung sai ±2dp xử lý làm tròn số. Nếu node có paddingLeft: 15 và spacing.16 tồn tại, agent sử dụng spacing.16. Nếu token gần nhất là spacing.24, không khớp — agent dùng giá trị thực.
Với màu sắc, khớp chính xác theo giá trị hex sau khi chuẩn hóa về 6 chữ số viết thường. #7F5CFE khớp với color.7f5cfe.
Với corner radius, áp dụng quy tắc ±2dp tương tự như spacing.
Kết quả thực tế cho mục tiêu Jetpack Compose trông như thế này:
// Với tokensSource là figma-variables
Surface(
shape = RoundedCornerShape(Radius.radius8),
color = Color.Brand.Primary
) {
Column(
verticalArrangement = Arrangement.spacedBy(Spacing.spacing16),
modifier = Modifier.padding(horizontal = Spacing.spacing24)
) { ... }
}
// Với tokensSource là inferred-from-frequency (cùng output trực quan, cùng tham chiếu token)
Surface(
shape = RoundedCornerShape(Radius.radius8),
color = Color.color7f5cfe
) { ... }
Tên dẫn xuất từ giá trị ít dễ đọc hơn. Nhưng vẫn tốt hơn giá trị hardcoded.
So sánh với các phương pháp trích xuất token khác
Panel "Inspect" gốc của Figma hiển thị giá trị theo từng node. Không có file token được xuất. Bạn phải tạo thủ công hoặc dùng plugin như Tokens Studio. Cả hai đều cần nỗ lực từ designer và bảo trì liên tục.
figmascope trích xuất token tự động ở mỗi lần xuất. Nếu file thay đổi, xuất lại và token phản ánh trạng thái hiện tại. Sự đánh đổi là nếu không có Variables, tên là dẫn xuất từ giá trị thay vì ngữ nghĩa — nhưng bạn luôn có file token mỗi lần, không cần plugin hay bước workflow thêm.
Tokens Studio (trước đây tích hợp Style Dictionary) yêu cầu designer thiết lập plugin, duy trì file JSON trong file Figma và đồng bộ hóa. Đó là giải pháp đúng cho design system trưởng thành. Cách tiếp cận của figmascope là giải pháp đúng cho các file chưa đạt đến đó — tức là hầu hết các file.
Việc trích xuất token là ảnh chụp nhanh tốt nhất có thể của những gì tồn tại trong file. Đây không phải thay thế cho design system ưu tiên token. Đây là thứ bạn dùng trong khi xây dựng hướng tới một hệ thống như vậy.
Kết nối tokens.json với codebase của bạn
Cấu trúc JSON đủ phẳng để tạo Kotlin object hoặc TypeScript module từ nó khá đơn giản. Một script đơn giản:
// tokens.json → Kotlin object (đơn giản hóa)
const tokens = JSON.parse(fs.readFileSync('tokens.json', 'utf-8'));
let output = 'object Spacing {\n';
for (const [key, token] of Object.entries(tokens.spacing)) {
const name = key.replace('spacing.', 'spacing').replace('.', '_');
output += ` val ${name} = ${token.$value}.dp\n`;
}
output += '}';
// → val spacing16 = 16.dp
Nếu bạn đang sử dụng pipeline design token sẵn có, định dạng gần chuẩn W3C đủ gần để tích hợp vào Style Dictionary với transformer tùy chỉnh cho các key $value/$type.
Phần còn lại về cách token tương tác với IR được đề cập trong Per-Screen IR. Để hiểu cách token tương tác với ngữ cảnh agent rộng hơn, xem Anatomy of CONTEXT.md. Để hiểu quy trình xuất design token từ đầu đến cuối, xem Design Token Export. Để xuất token từ file Figma của bạn, truy cập figmascope.dev.